Merge "Update docmentation to include segmented button in 1.2.0-alpha04." into androidx-main
diff --git a/activity/activity/lint-baseline.xml b/activity/activity/lint-baseline.xml
index c33c4d5..6c10a57 100644
--- a/activity/activity/lint-baseline.xml
+++ b/activity/activity/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="cli" dependencies="false" name="AGP (8.2.0-alpha14)" variant="all" version="8.2.0-alpha14">
+<issues format="6" by="lint 8.3.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
 
     <issue
         id="NewApi"
@@ -28,40 +28,4 @@
             file="src/main/java/androidx/activity/result/PickVisualMediaRequest.kt"/>
     </issue>
 
-    <issue
-        id="NewApi"
-        message="Field requires API level 19 (current min is 14): `VideoOnly`"
-        errorLine1="            ActivityResultContracts.PickVisualMedia.VideoOnly"
-        errorLine2="                                                    ~~~~~~~~~">
-        <location
-            file="src/androidTest/java/androidx/activity/result/PickVisualMediaRequestTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 19 (current min is 14): `VideoOnly`"
-        errorLine1="        assertThat(request.mediaType).isEqualTo(ActivityResultContracts.PickVisualMedia.VideoOnly)"
-        errorLine2="                                                                                        ~~~~~~~~~">
-        <location
-            file="src/androidTest/java/androidx/activity/result/PickVisualMediaRequestTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 19 (current min is 14): `VideoOnly`"
-        errorLine1="        val request = PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.VideoOnly)"
-        errorLine2="                                                                                     ~~~~~~~~~">
-        <location
-            file="src/androidTest/java/androidx/activity/result/PickVisualMediaRequestTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Field requires API level 19 (current min is 14): `VideoOnly`"
-        errorLine1="        assertThat(request.mediaType).isEqualTo(ActivityResultContracts.PickVisualMedia.VideoOnly)"
-        errorLine2="                                                                                        ~~~~~~~~~">
-        <location
-            file="src/androidTest/java/androidx/activity/result/PickVisualMediaRequestTest.kt"/>
-    </issue>
-
 </issues>
diff --git a/appcompat/appcompat-lint/src/main/kotlin/androidx/appcompat/widget/SwitchUsageCodeDetector.kt b/appcompat/appcompat-lint/src/main/kotlin/androidx/appcompat/widget/SwitchUsageCodeDetector.kt
index 13fcd82..9ddb0e29 100644
--- a/appcompat/appcompat-lint/src/main/kotlin/androidx/appcompat/widget/SwitchUsageCodeDetector.kt
+++ b/appcompat/appcompat-lint/src/main/kotlin/androidx/appcompat/widget/SwitchUsageCodeDetector.kt
@@ -33,7 +33,7 @@
 class SwitchUsageCodeDetector : Detector(), Detector.UastScanner {
     companion object {
         private const val USING_CORE_SWITCH_DESCRIPTION =
-            "Use `SwitchCompat` from AppCompat or `SwitchMaterial` from Material library"
+            "Use `SwitchCompat` from AppCompat or `MaterialSwitch` from Material library"
 
         internal val USING_CORE_SWITCH_CODE: Issue = Issue.create(
             "UseSwitchCompatOrMaterialCode",
diff --git a/appcompat/appcompat-lint/src/main/kotlin/androidx/appcompat/widget/SwitchUsageXmlDetector.kt b/appcompat/appcompat-lint/src/main/kotlin/androidx/appcompat/widget/SwitchUsageXmlDetector.kt
index ad7f027..dbc5907 100644
--- a/appcompat/appcompat-lint/src/main/kotlin/androidx/appcompat/widget/SwitchUsageXmlDetector.kt
+++ b/appcompat/appcompat-lint/src/main/kotlin/androidx/appcompat/widget/SwitchUsageXmlDetector.kt
@@ -31,7 +31,7 @@
         internal val USING_CORE_SWITCH_XML: Issue = Issue.create(
             "UseSwitchCompatOrMaterialXml",
             "Replace usage of `Switch` widget",
-            "Use `SwitchCompat` from AppCompat or `SwitchMaterial` from Material library",
+            "Use `SwitchCompat` from AppCompat or `MaterialSwitch` from Material library",
             Category.CORRECTNESS,
             5,
             Severity.WARNING,
@@ -46,7 +46,7 @@
             USING_CORE_SWITCH_XML,
             element,
             context.getLocation(element),
-            "Use `SwitchCompat` from AppCompat or `SwitchMaterial` from Material library"
+            "Use `SwitchCompat` from AppCompat or `MaterialSwitch` from Material library"
         )
     }
 }
diff --git a/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/integ/UseCompatDetectorTest.kt b/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/integ/UseCompatDetectorTest.kt
index f8f1b62..f37e319 100644
--- a/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/integ/UseCompatDetectorTest.kt
+++ b/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/integ/UseCompatDetectorTest.kt
@@ -33,7 +33,7 @@
 
         /* ktlint-disable max-line-length */
         val expected = """
-src/com/example/android/appcompat/AppCompatLintDemo.java:68: Warning: Use SwitchCompat from AppCompat or SwitchMaterial from Material library [UseSwitchCompatOrMaterialCode]
+src/com/example/android/appcompat/AppCompatLintDemo.java:68: Warning: Use SwitchCompat from AppCompat or MaterialSwitch from Material library [UseSwitchCompatOrMaterialCode]
         Switch mySwitch = new Switch(this);
         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 src/com/example/android/appcompat/AppCompatLintDemo.java:63: Warning: Use TextViewCompat.setCompoundDrawableTintList() [UseCompatTextViewDrawableApis]
@@ -57,7 +57,7 @@
 
         /* ktlint-disable max-line-length */
         val expected = """
-src/com/example/android/appcompat/CustomSwitch.java:27: Warning: Use SwitchCompat from AppCompat or SwitchMaterial from Material library [UseSwitchCompatOrMaterialCode]
+src/com/example/android/appcompat/CustomSwitch.java:27: Warning: Use SwitchCompat from AppCompat or MaterialSwitch from Material library [UseSwitchCompatOrMaterialCode]
 public class CustomSwitch extends Switch {
                                   ~~~~~~
 0 errors, 1 warnings
diff --git a/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/widget/SwitchUsageCodeDetectorTest.kt b/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/widget/SwitchUsageCodeDetectorTest.kt
index bd89439..12b9f75 100644
--- a/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/widget/SwitchUsageCodeDetectorTest.kt
+++ b/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/widget/SwitchUsageCodeDetectorTest.kt
@@ -46,7 +46,7 @@
             .run()
             .expect(
                 """
-src/com/example/CustomSwitch.kt:6: Warning: Use SwitchCompat from AppCompat or SwitchMaterial from Material library [UseSwitchCompatOrMaterialCode]
+src/com/example/CustomSwitch.kt:6: Warning: Use SwitchCompat from AppCompat or MaterialSwitch from Material library [UseSwitchCompatOrMaterialCode]
 class CustomSwitch(context: Context): Switch(context)
                                       ~~~~~~
 0 errors, 1 warnings
@@ -84,7 +84,7 @@
             .run()
             .expect(
                 """
-src/com/example/CustomActivity.kt:9: Warning: Use SwitchCompat from AppCompat or SwitchMaterial from Material library [UseSwitchCompatOrMaterialCode]
+src/com/example/CustomActivity.kt:9: Warning: Use SwitchCompat from AppCompat or MaterialSwitch from Material library [UseSwitchCompatOrMaterialCode]
         val mySwitch = Switch(this)
         ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 0 errors, 1 warnings
diff --git a/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/widget/SwitchUsageXmlDetectorTest.kt b/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/widget/SwitchUsageXmlDetectorTest.kt
index 4064a90..302da83 100644
--- a/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/widget/SwitchUsageXmlDetectorTest.kt
+++ b/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/widget/SwitchUsageXmlDetectorTest.kt
@@ -73,7 +73,7 @@
             .run()
             .expect(
                 """
-res/layout/switch.xml:6: Warning: Use SwitchCompat from AppCompat or SwitchMaterial from Material library [UseSwitchCompatOrMaterialXml]
+res/layout/switch.xml:6: Warning: Use SwitchCompat from AppCompat or MaterialSwitch from Material library [UseSwitchCompatOrMaterialXml]
     <Switch
     ^
 0 errors, 1 warnings
diff --git a/appcompat/appcompat/lint-baseline.xml b/appcompat/appcompat/lint-baseline.xml
index 35c6cb0..d2600ce 100644
--- a/appcompat/appcompat/lint-baseline.xml
+++ b/appcompat/appcompat/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-beta02" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta02)" variant="all" version="8.1.0-beta02">
+<issues format="6" by="lint 8.3.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
 
     <issue
         id="NewApi"
@@ -75,6 +75,24 @@
 
     <issue
         id="NewApi"
+        message="Call requires API level 17 (current min is 14): `setLocalNightMode`"
+        errorLine1="        delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_YES"
+        errorLine2="                 ~~~~~~~~~~~~~~">
+        <location
+            file="src/androidTest/java/androidx/appcompat/app/NightModeLocalBeforeAttachBaseActivity.kt"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Field requires API level 17 (current min is 14): `setLocalNightMode`"
+        errorLine1="        delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_YES"
+        errorLine2="                 ~~~~~~~~~~~~~~">
+        <location
+            file="src/androidTest/java/androidx/appcompat/app/NightModeLocalBeforeAttachBaseActivity.kt"/>
+    </issue>
+
+    <issue
+        id="NewApi"
         message="Call requires API level 17 (current min is 16): `android.content.res.Configuration#setLayoutDirection`"
         errorLine1="        configuration.setLayoutDirection(locale)"
         errorLine2="                      ~~~~~~~~~~~~~~~~~~">
@@ -93,6 +111,15 @@
 
     <issue
         id="NewApi"
+        message="Call requires API level 17 (current min is 16): `android.content.res.Configuration#getLayoutDirection`"
+        errorLine1="        assertEquals(TextUtils.getLayoutDirectionFromLocale(CUSTOM_LOCALE), config.layoutDirection)"
+        errorLine2="                                                                                   ~~~~~~~~~~~~~~~">
+        <location
+            file="src/androidTest/java/androidx/appcompat/app/NightModeRtlTestUtilsRegressionTestCase.kt"/>
+    </issue>
+
+    <issue
+        id="NewApi"
         message="Call requires API level 17 (current min is 16): `android.text.TextUtils#getLayoutDirectionFromLocale`"
         errorLine1="        assertEquals(TextUtils.getLayoutDirectionFromLocale(CUSTOM_LOCALE), config.layoutDirection)"
         errorLine2="                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/DocumentIdUtilCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/DocumentIdUtilCtsTest.java
index 89d2180..f3cb395 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/DocumentIdUtilCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/DocumentIdUtilCtsTest.java
@@ -23,6 +23,7 @@
 
 import org.junit.Test;
 
+/*@exportToFramework:SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)*/
 public class DocumentIdUtilCtsTest {
 
     @Test
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/JoinSpecCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/JoinSpecCtsTest.java
index a58b892..c04d448 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/JoinSpecCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/JoinSpecCtsTest.java
@@ -23,6 +23,7 @@
 
 import org.junit.Test;
 
+/*@exportToFramework:SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)*/
 public class JoinSpecCtsTest {
 
     @Test
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/PropertyPathCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/PropertyPathCtsTest.java
index b7ed1f8..69a94d3 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/PropertyPathCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/PropertyPathCtsTest.java
@@ -17,7 +17,6 @@
 package androidx.appsearch.cts.app;
 
 import static androidx.appsearch.app.PropertyPath.PathSegment.NON_REPEATED_CARDINALITY;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import androidx.appsearch.app.PropertyPath;
@@ -30,6 +29,7 @@
 import java.util.Iterator;
 import java.util.List;
 
+/*@exportToFramework:SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)*/
 public class PropertyPathCtsTest {
     @Test
     public void testPropertyPathInvalid() {
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchResultCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchResultCtsTest.java
index 426d01a..24d0153 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchResultCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchResultCtsTest.java
@@ -17,7 +17,6 @@
 package androidx.appsearch.cts.app;
 
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.junit.Assert.assertThrows;
 
 import androidx.appsearch.app.PropertyPath;
@@ -107,6 +106,7 @@
     }
 
     @Test
+    /*@exportToFramework:SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)*/
     public void testJoinedDocument() {
         AppSearchEmail email = new AppSearchEmail.Builder("namespace1", "id1")
                 .setBody("Hello World.")
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchSuggestionResultCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchSuggestionResultCtsTest.java
index 66fb2ad..675b507 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchSuggestionResultCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchSuggestionResultCtsTest.java
@@ -22,6 +22,7 @@
 
 import org.junit.Test;
 
+/*@exportToFramework:SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)*/
 public class SearchSuggestionResultCtsTest {
     @Test
     public void testBuildDefaultSearchSuggestionResult() {
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchSuggestionSpecCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchSuggestionSpecCtsTest.java
index 69979c3..d3dd5c3 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchSuggestionSpecCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/SearchSuggestionSpecCtsTest.java
@@ -17,7 +17,6 @@
 package androidx.appsearch.cts.app;
 
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.junit.Assert.assertThrows;
 
 import androidx.appsearch.app.SearchSuggestionSpec;
@@ -26,6 +25,7 @@
 
 import org.junit.Test;
 
+/*@exportToFramework:SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)*/
 public class SearchSuggestionSpecCtsTest {
     @Test
     public void testBuildDefaultSearchSuggestionSpec() throws Exception {
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/AnnotatedGetterOrField.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/AnnotatedGetterOrField.java
index 712cb2b..9208237 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/AnnotatedGetterOrField.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/AnnotatedGetterOrField.java
@@ -20,7 +20,6 @@
 import static androidx.appsearch.compiler.IntrospectionHelper.getDocumentAnnotation;
 import static androidx.appsearch.compiler.IntrospectionHelper.getPropertyType;
 import static androidx.appsearch.compiler.IntrospectionHelper.validateIsGetter;
-
 import static java.util.Objects.requireNonNull;
 import static java.util.stream.Collectors.joining;
 
@@ -360,7 +359,7 @@
         List<? extends AnnotationMirror> annotations =
                 element.getAnnotationMirrors().stream()
                         .filter(ann -> ann.getAnnotationType().toString().startsWith(
-                                DOCUMENT_ANNOTATION_CLASS)).toList();
+                                DOCUMENT_ANNOTATION_CLASS.canonicalName())).toList();
         if (annotations.isEmpty()) {
             return null;
         }
@@ -533,7 +532,7 @@
                 .anyMatch(expectedType -> typeUtils.isSameType(expectedType, target));
         if (!isValid) {
             String error = "@"
-                    + getterOrField.getAnnotation().getSimpleClassName()
+                    + getterOrField.getAnnotation().getClassName().simpleName()
                     + " must only be placed on a getter/field of type "
                     + (allowRepeated ? "or array or collection of " : "")
                     + expectedTypes.stream().map(TypeMirror::toString).collect(joining("|"));
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/AppSearchCompiler.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/AppSearchCompiler.java
index f15e793..4ea0f1d6 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/AppSearchCompiler.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/AppSearchCompiler.java
@@ -15,6 +15,8 @@
  */
 package androidx.appsearch.compiler;
 
+import static androidx.appsearch.compiler.IntrospectionHelper.APPSEARCH_ANNOTATION_PKG;
+import static androidx.appsearch.compiler.IntrospectionHelper.DOCUMENT_ANNOTATION_SIMPLE_CLASS_NAME;
 import static javax.lang.model.util.ElementFilter.typesIn;
 
 import androidx.annotation.NonNull;
@@ -47,7 +49,7 @@
  *
  * <p>Only plain Java objects and AutoValue Document classes without builders are supported.
  */
-@SupportedAnnotationTypes({IntrospectionHelper.DOCUMENT_ANNOTATION_CLASS})
+@SupportedAnnotationTypes({APPSEARCH_ANNOTATION_PKG + "." + DOCUMENT_ANNOTATION_SIMPLE_CLASS_NAME})
 @SupportedSourceVersion(SourceVersion.RELEASE_8)
 @SupportedOptions({AppSearchCompiler.OUTPUT_DIR_OPTION})
 public class AppSearchCompiler extends BasicAnnotationProcessor {
@@ -80,7 +82,7 @@
 
         @Override
         public ImmutableSet<String> annotations() {
-            return ImmutableSet.of(IntrospectionHelper.DOCUMENT_ANNOTATION_CLASS);
+            return ImmutableSet.of(IntrospectionHelper.DOCUMENT_ANNOTATION_CLASS.canonicalName());
         }
 
         @Override
@@ -88,7 +90,7 @@
                 ImmutableSetMultimap<String, Element> elementsByAnnotation) {
             Set<TypeElement> documentElements =
                     typesIn(elementsByAnnotation.get(
-                            IntrospectionHelper.DOCUMENT_ANNOTATION_CLASS));
+                            IntrospectionHelper.DOCUMENT_ANNOTATION_CLASS.canonicalName()));
 
             ImmutableSet.Builder<Element> nextRound = new ImmutableSet.Builder<>();
             for (TypeElement document : documentElements) {
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentModel.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentModel.java
index 925d0bb..93116a9 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentModel.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/DocumentModel.java
@@ -19,8 +19,6 @@
 import static androidx.appsearch.compiler.IntrospectionHelper.DOCUMENT_ANNOTATION_CLASS;
 import static androidx.appsearch.compiler.IntrospectionHelper.generateClassHierarchy;
 import static androidx.appsearch.compiler.IntrospectionHelper.getDocumentAnnotation;
-import static androidx.appsearch.compiler.IntrospectionHelper.validateIsGetter;
-
 import static java.util.stream.Collectors.groupingBy;
 
 import androidx.annotation.NonNull;
@@ -32,6 +30,7 @@
 import androidx.appsearch.compiler.annotationwrapper.PropertyAnnotation;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumMap;
 import java.util.HashMap;
@@ -41,7 +40,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Optional;
 import java.util.Set;
 import java.util.function.Predicate;
 
@@ -53,8 +51,6 @@
 import javax.lang.model.element.Modifier;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.element.VariableElement;
-import javax.lang.model.type.TypeKind;
-import javax.lang.model.type.TypeMirror;
 import javax.lang.model.util.Elements;
 import javax.lang.model.util.Types;
 
@@ -69,9 +65,6 @@
     /** Enumeration of fields that must be handled specially (i.e. are not properties) */
     enum SpecialField {ID, NAMESPACE, CREATION_TIMESTAMP_MILLIS, TTL_MILLIS, SCORE}
 
-    /** Determines how the annotation processor has decided to read the value of a field. */
-    enum ReadKind {FIELD, GETTER}
-
     /** Determines how the annotation processor has decided to write the value of a field. */
     enum WriteKind {FIELD, SETTER, CREATION_METHOD}
 
@@ -89,12 +82,9 @@
     // for AutoValue document.
     // Warning: if you change this to a HashSet, we may choose different getters or setters from
     // run to run, causing the generated code to bounce.
-    private final Set<ExecutableElement> mAllMethods = new LinkedHashSet<>();
+    private final LinkedHashSet<ExecutableElement> mAllMethods;
     // All methods in the builder class, if a builder producer is provided.
-    private final Set<ExecutableElement> mAllBuilderMethods = new LinkedHashSet<>();
-    // Key: Name of the element which is accessed through the getter method.
-    // Value: ExecutableElement of the getter method.
-    private final Map<String, ExecutableElement> mGetterMethods = new HashMap<>();
+    private final LinkedHashSet<ExecutableElement> mAllBuilderMethods;
     // Key: Name of the element whose value is set through the setter method.
     // Value: ExecutableElement of the setter method.
     private final Map<String, ExecutableElement> mSetterMethods = new HashMap<>();
@@ -109,7 +99,6 @@
     // name
     private final Map<String, Element> mPropertyElements = new LinkedHashMap<>();
     private final Map<SpecialField, String> mSpecialFieldNames = new EnumMap<>(SpecialField.class);
-    private final Map<Element, ReadKind> mReadKinds = new HashMap<>();
     private final Map<Element, WriteKind> mWriteKinds = new HashMap<>();
     // Contains the reason why that element couldn't be written either by field or by setter.
     private final Map<Element, ProcessingException> mWriteWhyCreationMethod =
@@ -121,6 +110,15 @@
 
     private final List<AnnotatedGetterOrField> mAnnotatedGettersAndFields;
 
+    @NonNull
+    private final AnnotatedGetterOrField mIdAnnotatedGetterOrField;
+
+    @NonNull
+    private final AnnotatedGetterOrField mNamespaceAnnotatedGetterOrField;
+
+    @NonNull
+    private final Map<AnnotatedGetterOrField, PropertyAccessor> mAccessors;
+
     private DocumentModel(
             @NonNull ProcessingEnvironment env,
             @NonNull TypeElement clazz,
@@ -140,23 +138,24 @@
         mAnnotatedGettersAndFields = scanAnnotatedGettersAndFields(clazz, env);
 
         requireNoDuplicateMetadataProperties();
-        requireGetterOrFieldMatchingPredicate(
+        mIdAnnotatedGetterOrField = requireGetterOrFieldMatchingPredicate(
                 getterOrField -> getterOrField.getAnnotation() == MetadataPropertyAnnotation.ID,
                 /* errorMessage= */"All @Document classes must have exactly one field annotated "
                         + "with @Id");
-        requireGetterOrFieldMatchingPredicate(
+        mNamespaceAnnotatedGetterOrField = requireGetterOrFieldMatchingPredicate(
                 getterOrField ->
                         getterOrField.getAnnotation() == MetadataPropertyAnnotation.NAMESPACE,
                 /* errorMessage= */"All @Document classes must have exactly one field annotated "
                         + "with @Namespace");
 
+        mAllMethods = mHelper.getAllMethods(clazz);
+        mAccessors = inferPropertyAccessors(mAnnotatedGettersAndFields, mAllMethods, mHelper);
+
         // Scan methods and constructors. We will need this info when processing fields to
         // make sure the fields can be get and set.
         Set<ExecutableElement> potentialCreationMethods = extractCreationMethods(clazz);
-        addAllMethods(mClass, mAllMethods);
-        if (mBuilderClass != null) {
-            addAllMethods(mBuilderClass, mAllBuilderMethods);
-        }
+        mAllBuilderMethods = mBuilderClass != null
+                ? mHelper.getAllMethods(mBuilderClass) : new LinkedHashSet<>();
         scanFields(mClass);
         chooseCreationMethod(potentialCreationMethods);
     }
@@ -177,7 +176,7 @@
             boolean isAnnotated = false;
             for (AnnotationMirror annotation : child.getAnnotationMirrors()) {
                 if (annotation.getAnnotationType().toString().equals(
-                        IntrospectionHelper.BUILDER_PRODUCER_CLASS)) {
+                        IntrospectionHelper.BUILDER_PRODUCER_CLASS.canonicalName())) {
                     isAnnotated = true;
                     break;
                 }
@@ -256,7 +255,8 @@
             if (gettersAndFields.size() > 1) {
                 // Can show the error on any of the duplicates. Just pick the first first.
                 throw new ProcessingException(
-                        "Duplicate member annotated with @" + annotation.getSimpleClassName(),
+                        "Duplicate member annotated with @"
+                                + annotation.getClassName().simpleName(),
                         gettersAndFields.get(0).getElement());
             }
         }
@@ -266,16 +266,17 @@
      * Makes sure {@link #mAnnotatedGettersAndFields} contains a getter/field that matches the
      * predicate.
      *
+     * @return The matched getter/field.
      * @throws ProcessingException with the error message if no match.
      */
-    private void requireGetterOrFieldMatchingPredicate(
+    @NonNull
+    private AnnotatedGetterOrField requireGetterOrFieldMatchingPredicate(
             @NonNull Predicate<AnnotatedGetterOrField> predicate,
             @NonNull String errorMessage) throws ProcessingException {
-        Optional<AnnotatedGetterOrField> annotatedGetterOrField =
-                mAnnotatedGettersAndFields.stream().filter(predicate).findFirst();
-        if (annotatedGetterOrField.isEmpty()) {
-            throw new ProcessingException(errorMessage, mClass);
-        }
+        return mAnnotatedGettersAndFields.stream()
+                .filter(predicate)
+                .findFirst()
+                .orElseThrow(() -> new ProcessingException(errorMessage, mClass));
     }
 
     private Set<ExecutableElement> extractCreationMethods(TypeElement typeElement)
@@ -301,24 +302,6 @@
         return Collections.unmodifiableSet(creationMethods);
     }
 
-    private void addAllMethods(TypeElement typeElement, Set<ExecutableElement> allMethods) {
-        for (Element child : typeElement.getEnclosedElements()) {
-            if (child.getKind() == ElementKind.METHOD) {
-                allMethods.add((ExecutableElement) child);
-            }
-        }
-
-        TypeMirror superClass = typeElement.getSuperclass();
-        if (superClass.getKind().equals(TypeKind.DECLARED)) {
-            addAllMethods((TypeElement) mTypeUtil.asElement(superClass), allMethods);
-        }
-        for (TypeMirror implementedInterface : typeElement.getInterfaces()) {
-            if (implementedInterface.getKind().equals(TypeKind.DECLARED)) {
-                addAllMethods((TypeElement) mTypeUtil.asElement(implementedInterface), allMethods);
-            }
-        }
-    }
-
     /**
      * Tries to create an {@link DocumentModel} from the given {@link Element}.
      *
@@ -386,6 +369,35 @@
     }
 
     /**
+     * Returns the getter/field annotated with {@code @Document.Id}.
+     */
+    @NonNull
+    public AnnotatedGetterOrField getIdAnnotatedGetterOrField() {
+        return mIdAnnotatedGetterOrField;
+    }
+
+    /**
+     * Returns the getter/field annotated with {@code @Document.Namespace}.
+     */
+    @NonNull
+    public AnnotatedGetterOrField getNamespaceAnnotatedGetterOrField() {
+        return mNamespaceAnnotatedGetterOrField;
+    }
+
+    /**
+     * Returns the public/package-private accessor for an annotated getter/field (may be private).
+     */
+    @NonNull
+    public PropertyAccessor getAccessor(@NonNull AnnotatedGetterOrField getterOrField) {
+        PropertyAccessor accessor = mAccessors.get(getterOrField);
+        if (accessor == null) {
+            throw new IllegalArgumentException(
+                    "No such getter/field belongs to this DocumentModel: " + getterOrField);
+        }
+        return accessor;
+    }
+
+    /**
      * @deprecated Use {@link #getAnnotatedGettersAndFields()} instead.
      */
     @Deprecated
@@ -400,23 +412,12 @@
     }
 
     @Nullable
-    public ReadKind getElementReadKind(String elementName) {
-        Element element = mAllAppSearchElements.get(elementName);
-        return mReadKinds.get(element);
-    }
-
-    @Nullable
     public WriteKind getElementWriteKind(String elementName) {
         Element element = mAllAppSearchElements.get(elementName);
         return mWriteKinds.get(element);
     }
 
     @Nullable
-    public ExecutableElement getGetterForElement(String elementName) {
-        return mGetterMethods.get(elementName);
-    }
-
-    @Nullable
     public ExecutableElement getSetterForElement(String elementName) {
         return mSetterMethods.get(elementName);
     }
@@ -483,6 +484,26 @@
         return mBuilderClass;
     }
 
+    /**
+     * Infers the {@link PropertyAccessor} for each of the {@link AnnotatedGetterOrField}.
+     *
+     * <p>Each accessor may be the {@link AnnotatedGetterOrField} itself or some other non-private
+     * getter.
+     */
+    @NonNull
+    private static Map<AnnotatedGetterOrField, PropertyAccessor> inferPropertyAccessors(
+            @NonNull List<AnnotatedGetterOrField> annotatedGettersAndFields,
+            @NonNull Collection<ExecutableElement> allMethods,
+            @NonNull IntrospectionHelper helper) throws ProcessingException {
+        Map<AnnotatedGetterOrField, PropertyAccessor> accessors = new HashMap<>();
+        for (AnnotatedGetterOrField getterOrField : annotatedGettersAndFields) {
+            accessors.put(
+                    getterOrField,
+                    PropertyAccessor.infer(getterOrField, allMethods, helper));
+        }
+        return accessors;
+    }
+
     private boolean isFactoryMethod(ExecutableElement method) {
         Set<Modifier> methodModifiers = method.getModifiers();
         return methodModifiers.contains(Modifier.STATIC)
@@ -511,85 +532,85 @@
         // no annotation mirrors -> non-indexable field
         for (AnnotationMirror annotation : childElement.getAnnotationMirrors()) {
             String annotationFq = annotation.getAnnotationType().toString();
-            if (!annotationFq.startsWith(DOCUMENT_ANNOTATION_CLASS) || annotationFq.equals(
-                    BUILDER_PRODUCER_CLASS)) {
+            if (!annotationFq.startsWith(DOCUMENT_ANNOTATION_CLASS.canonicalName())
+                    || annotationFq.equals(BUILDER_PRODUCER_CLASS.canonicalName())) {
                 continue;
             }
             if (childElement.getKind() == ElementKind.CLASS) {
                 continue;
             }
 
-            switch (annotationFq) {
-                case IntrospectionHelper.ID_CLASS:
-                    if (mSpecialFieldNames.containsKey(SpecialField.ID)) {
-                        throw new ProcessingException(
-                                "Class hierarchy contains multiple fields annotated @Id",
-                                childElement);
-                    }
-                    mSpecialFieldNames.put(SpecialField.ID, fieldName);
-                    break;
-                case IntrospectionHelper.NAMESPACE_CLASS:
-                    if (mSpecialFieldNames.containsKey(SpecialField.NAMESPACE)) {
-                        throw new ProcessingException(
-                                "Class hierarchy contains multiple fields annotated @Namespace",
-                                childElement);
-                    }
-                    mSpecialFieldNames.put(SpecialField.NAMESPACE, fieldName);
-                    break;
-                case IntrospectionHelper.CREATION_TIMESTAMP_MILLIS_CLASS:
-                    if (mSpecialFieldNames.containsKey(SpecialField.CREATION_TIMESTAMP_MILLIS)) {
-                        throw new ProcessingException("Class hierarchy contains multiple fields "
-                                + "annotated @CreationTimestampMillis", childElement);
-                    }
-                    mSpecialFieldNames.put(
-                            SpecialField.CREATION_TIMESTAMP_MILLIS, fieldName);
-                    break;
-                case IntrospectionHelper.TTL_MILLIS_CLASS:
-                    if (mSpecialFieldNames.containsKey(SpecialField.TTL_MILLIS)) {
-                        throw new ProcessingException(
-                                "Class hierarchy contains multiple fields annotated @TtlMillis",
-                                childElement);
-                    }
-                    mSpecialFieldNames.put(SpecialField.TTL_MILLIS, fieldName);
-                    break;
-                case IntrospectionHelper.SCORE_CLASS:
-                    if (mSpecialFieldNames.containsKey(SpecialField.SCORE)) {
-                        throw new ProcessingException(
-                                "Class hierarchy contains multiple fields annotated @Score",
-                                childElement);
-                    }
-                    mSpecialFieldNames.put(SpecialField.SCORE, fieldName);
-                    break;
-                default:
-                    PropertyClass propertyClass = getPropertyClass(annotationFq);
-                    if (propertyClass != null) {
-                        // A property must either:
-                        //   1. be unique
-                        //   2. override a property from the Java parent while maintaining the same
-                        //      AppSearch property name
-                        checkFieldTypeForPropertyAnnotation(childElement, propertyClass);
-                        // It's assumed that parent types, in the context of Java's type system,
-                        // are always visited before child types, so existingProperty must come
-                        // from the parent type. To make this assumption valid, the result
-                        // returned by generateClassHierarchy must put parent types before child
-                        // types.
-                        Element existingProperty = mPropertyElements.get(fieldName);
-                        if (existingProperty != null) {
-                            if (!mTypeUtil.isSameType(
-                                    existingProperty.asType(), childElement.asType())) {
-                                throw new ProcessingException(
-                                        "Cannot override a property with a different type",
-                                        childElement);
-                            }
-                            if (!getPropertyName(existingProperty).equals(getPropertyName(
-                                    childElement))) {
-                                throw new ProcessingException(
-                                        "Cannot override a property with a different name",
-                                        childElement);
-                            }
+            if (annotationFq.equals(MetadataPropertyAnnotation.ID.getClassName().canonicalName())) {
+                if (mSpecialFieldNames.containsKey(SpecialField.ID)) {
+                    throw new ProcessingException(
+                            "Class hierarchy contains multiple fields annotated @Id",
+                            childElement);
+                }
+                mSpecialFieldNames.put(SpecialField.ID, fieldName);
+            } else if (annotationFq.equals(
+                    MetadataPropertyAnnotation.NAMESPACE.getClassName().canonicalName())) {
+                if (mSpecialFieldNames.containsKey(SpecialField.NAMESPACE)) {
+                    throw new ProcessingException(
+                            "Class hierarchy contains multiple fields annotated @Namespace",
+                            childElement);
+                }
+                mSpecialFieldNames.put(SpecialField.NAMESPACE, fieldName);
+            } else if (annotationFq.equals(
+                    MetadataPropertyAnnotation.CREATION_TIMESTAMP_MILLIS
+                            .getClassName()
+                            .canonicalName())) {
+                if (mSpecialFieldNames.containsKey(SpecialField.CREATION_TIMESTAMP_MILLIS)) {
+                    throw new ProcessingException("Class hierarchy contains multiple fields "
+                            + "annotated @CreationTimestampMillis", childElement);
+                }
+                mSpecialFieldNames.put(
+                        SpecialField.CREATION_TIMESTAMP_MILLIS, fieldName);
+            } else if (annotationFq.equals(
+                    MetadataPropertyAnnotation.TTL_MILLIS.getClassName().canonicalName())) {
+                if (mSpecialFieldNames.containsKey(SpecialField.TTL_MILLIS)) {
+                    throw new ProcessingException(
+                            "Class hierarchy contains multiple fields annotated @TtlMillis",
+                            childElement);
+                }
+                mSpecialFieldNames.put(SpecialField.TTL_MILLIS, fieldName);
+            } else if (annotationFq.equals(
+                    MetadataPropertyAnnotation.SCORE.getClassName().canonicalName())) {
+                if (mSpecialFieldNames.containsKey(SpecialField.SCORE)) {
+                    throw new ProcessingException(
+                            "Class hierarchy contains multiple fields annotated @Score",
+                            childElement);
+                }
+                mSpecialFieldNames.put(SpecialField.SCORE, fieldName);
+            } else {
+                PropertyClass propertyClass = getPropertyClass(annotationFq);
+                if (propertyClass != null) {
+                    // A property must either:
+                    //   1. be unique
+                    //   2. override a property from the Java parent while maintaining the same
+                    //      AppSearch property name
+                    checkFieldTypeForPropertyAnnotation(childElement, propertyClass);
+                    // It's assumed that parent types, in the context of Java's type system,
+                    // are always visited before child types, so existingProperty must come
+                    // from the parent type. To make this assumption valid, the result
+                    // returned by generateClassHierarchy must put parent types before child
+                    // types.
+                    Element existingProperty = mPropertyElements.get(fieldName);
+                    if (existingProperty != null) {
+                        if (!mTypeUtil.isSameType(
+                                existingProperty.asType(), childElement.asType())) {
+                            throw new ProcessingException(
+                                    "Cannot override a property with a different type",
+                                    childElement);
                         }
-                        mPropertyElements.put(fieldName, childElement);
+                        if (!getPropertyName(existingProperty).equals(getPropertyName(
+                                childElement))) {
+                            throw new ProcessingException(
+                                    "Cannot override a property with a different name",
+                                    childElement);
+                        }
                     }
+                    mPropertyElements.put(fieldName, childElement);
+                }
             }
 
             mAllAppSearchElements.put(fieldName, childElement);
@@ -648,7 +669,7 @@
         mSchemaName = computeSchemaName(hierarchy);
 
         for (Element appSearchField : mAllAppSearchElements.values()) {
-            chooseAccessKinds(appSearchField);
+            chooseWriteKind(appSearchField);
         }
     }
 
@@ -721,27 +742,15 @@
     }
 
     /**
-     * Chooses how to access the given field for read and write, subject to our requirements for all
-     * AppSearch-managed class fields:
+     * Chooses how to write a given field.
      *
-     * <p>For read: visible field, or visible getter
-     *
-     * <p>For write: visible mutable field, or visible setter, or visible creation method
-     * accepting at minimum all fields that aren't mutable and have no visible setter.
-     *
-     * @throws ProcessingException if no access type is possible for the given field
+     * <p>The writing strategy can be one of: visible mutable field, or visible setter, or visible
+     * creation method accepting at minimum all fields that aren't mutable and have no visible
+     * setter.
      */
-    private void chooseAccessKinds(@NonNull Element field)
-            throws ProcessingException {
-        // Choose get access
+    private void chooseWriteKind(@NonNull Element field) {
+        // TODO(b/300114568): Carve out better distinction b/w the different write strategies
         Set<Modifier> modifiers = field.getModifiers();
-        if (modifiers.contains(Modifier.PRIVATE) || field.getKind() == ElementKind.METHOD) {
-            findGetter(field);
-            mReadKinds.put(field, ReadKind.GETTER);
-        } else {
-            mReadKinds.put(field, ReadKind.FIELD);
-        }
-
         // Choose set access
         if (modifiers.contains(Modifier.PRIVATE) || modifiers.contains(Modifier.FINAL)
                 || modifiers.contains(Modifier.STATIC) || field.getKind() == ElementKind.METHOD
@@ -860,54 +869,6 @@
     }
 
     /**
-     * Finds getter function for a private field, or for a property defined by a annotated getter
-     * method, in which case the annotated element itself should be the getter unless it's
-     * private or it takes parameters.
-     */
-    private void findGetter(@NonNull Element element) throws ProcessingException {
-        String elementName = element.getSimpleName().toString();
-        ProcessingException e;
-        if (element.getKind() == ElementKind.METHOD) {
-            e = new ProcessingException(
-                    "Failed to find a suitable getter for element \"" + elementName + "\"",
-                    mAllAppSearchElements.get(elementName));
-        } else {
-            e = new ProcessingException(
-                    "Field cannot be read: it is private and we failed to find a suitable getter "
-                            + "for field \"" + elementName + "\"",
-                    mAllAppSearchElements.get(elementName));
-        }
-
-        for (ExecutableElement method : mAllMethods) {
-            String methodName = method.getSimpleName().toString();
-            String normalizedElementName = getNormalizedElementName(element);
-            // normalizedElementName with first letter capitalized, to be paired with [is] or [get]
-            // prefix
-            String methodNameSuffix = normalizedElementName.substring(0, 1).toUpperCase()
-                    + normalizedElementName.substring(1);
-
-            if (methodName.equals(normalizedElementName)
-                    || methodName.equals("get" + methodNameSuffix)
-                    || (
-                    mHelper.isFieldOfBooleanType(element)
-                            && methodName.equals("is" + methodNameSuffix))
-            ) {
-                List<ProcessingException> errors = validateIsGetter(method);
-                if (!errors.isEmpty()) {
-                    e.addWarnings(errors);
-                    continue;
-                }
-                // Found one!
-                mGetterMethods.put(elementName, method);
-                return;
-            }
-        }
-
-        // Broke out of the loop without finding anything.
-        throw e;
-    }
-
-    /**
      * Finds setter function for a private field, or for a property defined by a annotated getter
      * method.
      */
@@ -1251,11 +1212,9 @@
         }
 
         /**
-         * Returns the serialized name for the corresponding property in the database.
+         * Returns the serialized name that should be used for the property in the database.
          *
-         * <p>Assumes the getter/field is annotated with a {@link DataPropertyAnnotation} to pull
-         * the serialized name out of the annotation
-         * e.g. {@code @Document.StringProperty("serializedName")}.
+         * <p>Assumes the getter/field is annotated with a {@link DataPropertyAnnotation}.
          */
         @NonNull
         private static String getSerializedName(@NonNull AnnotatedGetterOrField getterOrField) {
@@ -1280,13 +1239,12 @@
                 throws ProcessingException {
             PropertyAnnotation existingAnnotation = existingGetterOrField.getAnnotation();
             PropertyAnnotation overriddenAnnotation = overriddenGetterOfField.getAnnotation();
-            if (!existingAnnotation.getQualifiedClassName().equals(
-                    overriddenAnnotation.getQualifiedClassName())) {
+            if (!existingAnnotation.getClassName().equals(overriddenAnnotation.getClassName())) {
                 throw new ProcessingException(
                         ("Property type must stay consistent when overriding annotated members "
                                 + "but changed from @%s -> @%s").formatted(
-                                existingAnnotation.getSimpleClassName(),
-                                overriddenAnnotation.getSimpleClassName()),
+                                existingAnnotation.getClassName().simpleName(),
+                                overriddenAnnotation.getClassName().simpleName()),
                         overriddenGetterOfField.getElement());
             }
         }
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/FromGenericDocumentCodeGenerator.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/FromGenericDocumentCodeGenerator.java
index e0e0b56..071fcd4 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/FromGenericDocumentCodeGenerator.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/FromGenericDocumentCodeGenerator.java
@@ -16,6 +16,7 @@
 
 package androidx.appsearch.compiler;
 
+import static androidx.appsearch.compiler.IntrospectionHelper.APPSEARCH_EXCEPTION_CLASS;
 import static androidx.appsearch.compiler.IntrospectionHelper.getDocumentAnnotation;
 import static androidx.appsearch.compiler.IntrospectionHelper.getPropertyType;
 
@@ -81,7 +82,7 @@
                 .returns(classType)
                 .addAnnotation(Override.class)
                 .addParameter(mHelper.getAppSearchClass("GenericDocument"), "genericDoc")
-                .addException(mHelper.getAppSearchExceptionClass());
+                .addException(APPSEARCH_EXCEPTION_CLASS);
 
         unpackSpecialFields(methodBuilder);
 
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java
index 420bd35..7e815f3 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/IntrospectionHelper.java
@@ -16,6 +16,7 @@
 package androidx.appsearch.compiler;
 
 import static com.google.auto.common.MoreTypes.asTypeElement;
+import static java.util.stream.Collectors.toCollection;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -30,6 +31,7 @@
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -60,18 +62,31 @@
 public class IntrospectionHelper {
     static final String GEN_CLASS_PREFIX = "$$__AppSearch__";
     static final String APPSEARCH_PKG = "androidx.appsearch.app";
+
+    public static final ClassName APPSEARCH_SCHEMA_CLASS =
+            ClassName.get(APPSEARCH_PKG, "AppSearchSchema");
+
+    static final ClassName PROPERTY_CONFIG_CLASS =
+            APPSEARCH_SCHEMA_CLASS.nestedClass("PropertyConfig");
+
     static final String APPSEARCH_EXCEPTION_PKG = "androidx.appsearch.exceptions";
-    static final String APPSEARCH_EXCEPTION_SIMPLE_NAME = "AppSearchException";
-    public static final String DOCUMENT_ANNOTATION_CLASS = "androidx.appsearch.annotation.Document";
-    static final String ID_CLASS = "androidx.appsearch.annotation.Document.Id";
-    static final String NAMESPACE_CLASS = "androidx.appsearch.annotation.Document.Namespace";
-    static final String CREATION_TIMESTAMP_MILLIS_CLASS =
-            "androidx.appsearch.annotation.Document.CreationTimestampMillis";
-    static final String TTL_MILLIS_CLASS = "androidx.appsearch.annotation.Document"
-            + ".TtlMillis";
-    static final String SCORE_CLASS = "androidx.appsearch.annotation.Document.Score";
-    static final String BUILDER_PRODUCER_CLASS =
-            "androidx.appsearch.annotation.Document.BuilderProducer";
+
+    static final ClassName APPSEARCH_EXCEPTION_CLASS =
+            ClassName.get(APPSEARCH_EXCEPTION_PKG, "AppSearchException");
+
+    public static final String APPSEARCH_ANNOTATION_PKG = "androidx.appsearch.annotation";
+
+    public static final String DOCUMENT_ANNOTATION_SIMPLE_CLASS_NAME = "Document";
+
+    public static final ClassName DOCUMENT_ANNOTATION_CLASS =
+            ClassName.get(APPSEARCH_ANNOTATION_PKG, DOCUMENT_ANNOTATION_SIMPLE_CLASS_NAME);
+
+    public static final ClassName GENERIC_DOCUMENT_CLASS =
+            ClassName.get(APPSEARCH_PKG, "GenericDocument");
+
+    public static final ClassName BUILDER_PRODUCER_CLASS =
+            DOCUMENT_ANNOTATION_CLASS.nestedClass("BuilderProducer");
+
     final TypeMirror mCollectionType;
     final TypeMirror mListType;
     final TypeMirror mStringType;
@@ -125,7 +140,8 @@
         Objects.requireNonNull(element);
         for (AnnotationMirror annotation : element.getAnnotationMirrors()) {
             String annotationFq = annotation.getAnnotationType().toString();
-            if (IntrospectionHelper.DOCUMENT_ANNOTATION_CLASS.equals(annotationFq)) {
+            if (IntrospectionHelper.DOCUMENT_ANNOTATION_CLASS.canonicalName().equals(
+                    annotationFq)) {
                 return annotation;
             }
         }
@@ -210,7 +226,9 @@
      * Creates the name of output class. $$__AppSearch__Foo for Foo, $$__AppSearch__Foo$$__Bar
      * for inner class Foo.Bar.
      */
-    public ClassName getDocumentClassFactoryForClass(String pkg, String className) {
+    @NonNull
+    public static ClassName getDocumentClassFactoryForClass(
+            @NonNull String pkg, @NonNull String className) {
         String genClassName = GEN_CLASS_PREFIX + className.replace(".", "$$__");
         return ClassName.get(pkg, genClassName);
     }
@@ -219,7 +237,8 @@
      * Creates the name of output class. $$__AppSearch__Foo for Foo, $$__AppSearch__Foo$$__Bar
      * for inner class Foo.Bar.
      */
-    public ClassName getDocumentClassFactoryForClass(ClassName clazz) {
+    @NonNull
+    public static ClassName getDocumentClassFactoryForClass(@NonNull ClassName clazz) {
         String className = clazz.canonicalName().substring(clazz.packageName().length() + 1);
         return getDocumentClassFactoryForClass(clazz.packageName(), className);
     }
@@ -228,8 +247,40 @@
         return ClassName.get(APPSEARCH_PKG, clazz, nested);
     }
 
-    public ClassName getAppSearchExceptionClass() {
-        return ClassName.get(APPSEARCH_EXCEPTION_PKG, APPSEARCH_EXCEPTION_SIMPLE_NAME);
+    /**
+     * Returns all the methods within a class, whether inherited or declared directly.
+     */
+    @NonNull
+    public LinkedHashSet<ExecutableElement> getAllMethods(@NonNull TypeElement clazz) {
+        return mEnv.getElementUtils().getAllMembers(clazz).stream()
+                .filter(element -> element.getKind() == ElementKind.METHOD)
+                .map(element -> (ExecutableElement) element)
+                .collect(toCollection(LinkedHashSet::new));
+    }
+
+    /**
+     * Whether a type is the same as {@code long[]}.
+     */
+    public boolean isPrimitiveLongArray(@NonNull TypeMirror type) {
+        return isArrayOf(type, mLongPrimitiveType);
+    }
+
+    /**
+     * Whether a type is the same as {@code double[]}.
+     */
+    public boolean isPrimitiveDoubleArray(@NonNull TypeMirror type) {
+        return isArrayOf(type, mDoublePrimitiveType);
+    }
+
+    /**
+     * Whether a type is the same as {@code boolean[]}.
+     */
+    public boolean isPrimitiveBooleanArray(@NonNull TypeMirror type) {
+        return isArrayOf(type, mBooleanPrimitiveType);
+    }
+
+    private boolean isArrayOf(@NonNull TypeMirror type, @NonNull TypeMirror arrayComponentType) {
+        return mTypeUtils.isSameType(type, mTypeUtils.getArrayType(arrayComponentType));
     }
 
     /**
@@ -284,6 +335,21 @@
         return errors;
     }
 
+    /**
+     * Same as {@link #validateIsGetter} but additionally verifies that the getter returns the
+     * specified type.
+     */
+    @NonNull
+    public List<ProcessingException> validateIsGetterThatReturns(
+            @NonNull ExecutableElement method, @NonNull TypeMirror expectedReturnType) {
+        List<ProcessingException> errors = validateIsGetter(method);
+        if (!mTypeUtils.isSameType(method.getReturnType(), expectedReturnType)) {
+            errors.add(new ProcessingException(
+                    "Getter cannot be used: Does not return " + expectedReturnType, method));
+        }
+        return errors;
+    }
+
     private static void generateClassHierarchyHelper(@NonNull TypeElement leafElement,
             @NonNull TypeElement currentClass, @NonNull Deque<TypeElement> hierarchy,
             @NonNull Set<TypeElement> visited)
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/PropertyAccessor.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/PropertyAccessor.java
new file mode 100644
index 0000000..28b3c05
--- /dev/null
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/PropertyAccessor.java
@@ -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.appsearch.compiler;
+
+import static java.util.stream.Collectors.joining;
+
+import androidx.annotation.NonNull;
+import androidx.appsearch.compiler.AnnotatedGetterOrField.ElementTypeCategory;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+
+/**
+ * The public/package-private accessor for an {@link AnnotatedGetterOrField}.
+ *
+ * <p>The accessor itself may be a getter or a field.
+ *
+ * <p>May be the {@link AnnotatedGetterOrField} itself or some completely different method in
+ * case the {@link AnnotatedGetterOrField} is private. For example:
+ *
+ * <pre>
+ * {@code
+ * @Document("MyEntity")
+ * class Entity {
+ *     @Document.StringProperty
+ *     private String mName;
+ *
+ *     public String getName();
+ *     //            ^^^^^^^
+ * }
+ * }
+ * </pre>
+ */
+@AutoValue
+public abstract class PropertyAccessor {
+
+    /**
+     * The getter/field element.
+     */
+    @NonNull
+    public abstract Element getElement();
+
+
+    /**
+     * Whether the accessor is a getter.
+     */
+    public boolean isGetter() {
+        return getElement().getKind() == ElementKind.METHOD;
+    }
+
+    /**
+     * Whether the accessor is a field.
+     */
+    public boolean isField() {
+        return getElement().getKind() == ElementKind.FIELD;
+    }
+
+    /**
+     * Infers the {@link PropertyAccessor} for a given {@link AnnotatedGetterOrField}.
+     *
+     * @param neighboringMethods The surrounding methods in the same class as the field. In case
+     *                           the field is private, an appropriate non-private getter can be
+     *                           picked from this list.
+     */
+    @NonNull
+    public static PropertyAccessor infer(
+            @NonNull AnnotatedGetterOrField getterOrField,
+            @NonNull Collection<ExecutableElement> neighboringMethods,
+            @NonNull IntrospectionHelper helper) throws ProcessingException {
+        if (!getterOrField.getElement().getModifiers().contains(Modifier.PRIVATE)) {
+            // Accessible as-is
+            return new AutoValue_PropertyAccessor(getterOrField.getElement());
+        }
+
+        if (getterOrField.isGetter()) {
+            throw new ProcessingException(
+                    "Annotated getter must not be private", getterOrField.getElement());
+        }
+
+        return new AutoValue_PropertyAccessor(
+                findCorrespondingGetter(getterOrField, neighboringMethods, helper));
+    }
+
+    @NonNull
+    private static ExecutableElement findCorrespondingGetter(
+            @NonNull AnnotatedGetterOrField privateField,
+            @NonNull Collection<ExecutableElement> neighboringMethods,
+            @NonNull IntrospectionHelper helper) throws ProcessingException {
+        Set<String> getterNames = getAcceptableGetterNames(privateField, helper);
+        List<ExecutableElement> potentialGetters =
+                neighboringMethods.stream()
+                        .filter(method -> getterNames.contains(method.getSimpleName().toString()))
+                        .toList();
+
+        // Start building the exception for the case where we don't find a suitable getter
+        String potentialSignatures = getterNames.stream()
+                .map(name -> "[public] " + privateField.getJvmType() + " " + name + "()")
+                .collect(joining(" OR "));
+        ProcessingException processingException = new ProcessingException(
+                "Field '%s' cannot be read: it is private and has no suitable getters %s"
+                        .formatted(privateField.getJvmName(), potentialSignatures),
+                privateField.getElement());
+
+        for (ExecutableElement method : potentialGetters) {
+            List<ProcessingException> errors =
+                    helper.validateIsGetterThatReturns(method, privateField.getJvmType());
+            if (!errors.isEmpty()) {
+                processingException.addWarnings(errors);
+                continue;
+            }
+            // found one!
+            return method;
+        }
+
+        throw processingException;
+    }
+
+    @NonNull
+    private static Set<String> getAcceptableGetterNames(
+            @NonNull AnnotatedGetterOrField privateField,
+            @NonNull IntrospectionHelper helper) {
+        // String mMyField -> {myField, getMyField}
+        // boolean mMyField -> {myField, getMyField, isMyField}
+        String normalizedName = privateField.getNormalizedName();
+        Set<String> getterNames = new HashSet<>();
+        getterNames.add(normalizedName);
+        String upperCamelCase = normalizedName.substring(0, 1).toUpperCase()
+                + normalizedName.substring(1);
+        getterNames.add("get" + upperCamelCase);
+        boolean isBooleanField = helper.isFieldOfExactType(
+                privateField.getElement(),
+                helper.mBooleanPrimitiveType,
+                helper.mBooleanBoxType);
+        if (isBooleanField && privateField.getElementTypeCategory() == ElementTypeCategory.SINGLE) {
+            getterNames.add("is" + upperCamelCase);
+        }
+        return getterNames;
+    }
+}
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/SchemaCodeGenerator.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/SchemaCodeGenerator.java
index 8473ed4..288a61b 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/SchemaCodeGenerator.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/SchemaCodeGenerator.java
@@ -16,9 +16,16 @@
 
 package androidx.appsearch.compiler;
 
-import static androidx.appsearch.compiler.IntrospectionHelper.getPropertyType;
+import static androidx.appsearch.compiler.IntrospectionHelper.APPSEARCH_EXCEPTION_CLASS;
+import static androidx.appsearch.compiler.IntrospectionHelper.APPSEARCH_SCHEMA_CLASS;
+import static androidx.appsearch.compiler.IntrospectionHelper.PROPERTY_CONFIG_CLASS;
+import static androidx.appsearch.compiler.IntrospectionHelper.getDocumentClassFactoryForClass;
 
 import androidx.annotation.NonNull;
+import androidx.appsearch.compiler.annotationwrapper.DataPropertyAnnotation;
+import androidx.appsearch.compiler.annotationwrapper.DocumentPropertyAnnotation;
+import androidx.appsearch.compiler.annotationwrapper.LongPropertyAnnotation;
+import androidx.appsearch.compiler.annotationwrapper.StringPropertyAnnotation;
 
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.CodeBlock;
@@ -29,41 +36,45 @@
 import com.squareup.javapoet.TypeSpec;
 import com.squareup.javapoet.WildcardTypeName;
 
+import java.util.Collections;
 import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
 
 import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.Element;
 import javax.lang.model.element.Modifier;
 import javax.lang.model.element.TypeElement;
-import javax.lang.model.type.ArrayType;
-import javax.lang.model.type.DeclaredType;
-import javax.lang.model.type.TypeKind;
 import javax.lang.model.type.TypeMirror;
-import javax.lang.model.util.Types;
 
 /** Generates java code for an {@link androidx.appsearch.app.AppSearchSchema}. */
 class SchemaCodeGenerator {
-    private final ProcessingEnvironment mEnv;
-    private final IntrospectionHelper mHelper;
     private final DocumentModel mModel;
-    private final Set<ClassName> mDependencyDocumentClasses = new LinkedHashSet<>();
+    private final LinkedHashSet<TypeElement> mDependencyDocumentClasses;
 
     public static void generate(
             @NonNull ProcessingEnvironment env,
             @NonNull DocumentModel model,
             @NonNull TypeSpec.Builder classBuilder) throws ProcessingException {
-        new SchemaCodeGenerator(env, model).generate(classBuilder);
+        new SchemaCodeGenerator(model, env).generate(classBuilder);
     }
 
-    private SchemaCodeGenerator(
-            @NonNull ProcessingEnvironment env, @NonNull DocumentModel model) {
-        mEnv = env;
-        mHelper = new IntrospectionHelper(env);
+    private SchemaCodeGenerator(@NonNull DocumentModel model, @NonNull ProcessingEnvironment env) {
         mModel = model;
+        mDependencyDocumentClasses = computeDependencyClasses(model, env);
+    }
+
+    @NonNull
+    private static LinkedHashSet<TypeElement> computeDependencyClasses(
+            @NonNull DocumentModel model,
+            @NonNull ProcessingEnvironment env) {
+        LinkedHashSet<TypeElement> dependencies = new LinkedHashSet<>(model.getParentTypes());
+        for (AnnotatedGetterOrField getterOrField : model.getAnnotatedGettersAndFields()) {
+            if (!(getterOrField.getAnnotation() instanceof DocumentPropertyAnnotation)) {
+                continue;
+            }
+
+            TypeMirror documentClass = getterOrField.getComponentType();
+            dependencies.add((TypeElement) env.getTypeUtils().asElement(documentClass));
+        }
+        return dependencies;
     }
 
     private void generate(@NonNull TypeSpec.Builder classBuilder) throws ProcessingException {
@@ -76,20 +87,18 @@
         classBuilder.addMethod(
                 MethodSpec.methodBuilder("getSchemaName")
                         .addModifiers(Modifier.PUBLIC)
-                        .returns(TypeName.get(mHelper.mStringType))
+                        .returns(String.class)
                         .addAnnotation(Override.class)
                         .addStatement("return SCHEMA_NAME")
                         .build());
 
-        CodeBlock schemaInitializer = createSchemaInitializerGetDocumentTypes();
-
         classBuilder.addMethod(
                 MethodSpec.methodBuilder("getSchema")
                         .addModifiers(Modifier.PUBLIC)
-                        .returns(mHelper.getAppSearchClass("AppSearchSchema"))
+                        .returns(APPSEARCH_SCHEMA_CLASS)
                         .addAnnotation(Override.class)
-                        .addException(mHelper.getAppSearchExceptionClass())
-                        .addStatement("return $L", schemaInitializer)
+                        .addException(APPSEARCH_EXCEPTION_CLASS)
+                        .addStatement("return $L", createSchemaInitializerGetDocumentTypes())
                         .build());
 
         classBuilder.addMethod(createDependencyClassesMethod());
@@ -97,239 +106,280 @@
 
     @NonNull
     private MethodSpec createDependencyClassesMethod() {
-        TypeName setOfClasses = ParameterizedTypeName.get(ClassName.get("java.util", "List"),
+        TypeName listOfClasses = ParameterizedTypeName.get(ClassName.get("java.util", "List"),
                 ParameterizedTypeName.get(ClassName.get(Class.class),
                         WildcardTypeName.subtypeOf(Object.class)));
 
-        TypeName arraySetOfClasses =
+        TypeName arrayListOfClasses =
                 ParameterizedTypeName.get(ClassName.get("java.util", "ArrayList"),
                         ParameterizedTypeName.get(ClassName.get(Class.class),
                                 WildcardTypeName.subtypeOf(Object.class)));
 
         MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("getDependencyDocumentClasses")
                 .addModifiers(Modifier.PUBLIC)
-                .returns(setOfClasses)
+                .returns(listOfClasses)
                 .addAnnotation(Override.class)
-                .addException(mHelper.getAppSearchExceptionClass());
+                .addException(APPSEARCH_EXCEPTION_CLASS);
 
         if (mDependencyDocumentClasses.isEmpty()) {
-            methodBuilder.addStatement("return $T.emptyList()",
-                    ClassName.get("java.util", "Collections"));
+            methodBuilder.addStatement("return $T.emptyList()", ClassName.get(Collections.class));
         } else {
-            methodBuilder.addStatement("$T classSet = new $T()", setOfClasses, arraySetOfClasses);
-            for (ClassName className : mDependencyDocumentClasses) {
-                methodBuilder.addStatement("classSet.add($T.class)", className);
+            methodBuilder.addStatement("$T classSet = new $T()", listOfClasses, arrayListOfClasses);
+            for (TypeElement dependencyType : mDependencyDocumentClasses) {
+                methodBuilder.addStatement("classSet.add($T.class)", ClassName.get(dependencyType));
             }
             methodBuilder.addStatement("return classSet").build();
         }
+
         return methodBuilder.build();
     }
 
     /**
-     * This method accumulates Document-type properties, by calling {@link #createPropertySchema},
-     * and parent types in mDependencyDocumentClasses.
+     * Creates an expr of type {@link androidx.appsearch.app.AppSearchSchema}.
+     *
+     * <p>The AppSearchSchema has parent types and various Document.*Properties set.
      */
     private CodeBlock createSchemaInitializerGetDocumentTypes() throws ProcessingException {
         CodeBlock.Builder codeBlock = CodeBlock.builder()
-                .add("new $T(SCHEMA_NAME)", mHelper.getAppSearchClass("AppSearchSchema", "Builder"))
+                .add("new $T(SCHEMA_NAME)", APPSEARCH_SCHEMA_CLASS.nestedClass("Builder"))
                 .indent();
         for (TypeElement parentType : mModel.getParentTypes()) {
             ClassName parentDocumentFactoryClass =
-                    mHelper.getDocumentClassFactoryForClass(ClassName.get(parentType));
+                    getDocumentClassFactoryForClass(ClassName.get(parentType));
             codeBlock.add("\n.addParentType($T.SCHEMA_NAME)", parentDocumentFactoryClass);
-            mDependencyDocumentClasses.add(ClassName.get(parentType));
         }
-        for (Element property : mModel.getPropertyElements().values()) {
-            codeBlock.add("\n.addProperty($L)", createPropertySchema(property));
+
+        for (AnnotatedGetterOrField getterOrField : mModel.getAnnotatedGettersAndFields()) {
+            if (!(getterOrField.getAnnotation() instanceof DataPropertyAnnotation)) {
+                continue;
+            }
+
+            CodeBlock propertyConfigExpr = createPropertyConfig(
+                    (DataPropertyAnnotation) getterOrField.getAnnotation(), getterOrField);
+            codeBlock.add("\n.addProperty($L)", propertyConfigExpr);
         }
+
         codeBlock.add("\n.build()").unindent();
         return codeBlock.build();
     }
 
-    /** This method accumulates Document-type properties in mDependencyDocumentClasses. */
-    private CodeBlock createPropertySchema(@NonNull Element property)
-            throws ProcessingException {
-        AnnotationMirror annotation = mModel.getPropertyAnnotation(property);
-        Map<String, Object> params = mHelper.getAnnotationParams(annotation);
-
-        // Find the property type
-        Types typeUtil = mEnv.getTypeUtils();
-        TypeMirror propertyType = getPropertyType(property);
-        boolean repeated = false;
-        boolean isPropertyString = false;
-        boolean isPropertyDocument = false;
-        boolean isPropertyLong = false;
-        if (propertyType.getKind() == TypeKind.ERROR) {
-            throw new ProcessingException("Property type unknown to java compiler", property);
-        } else if (typeUtil.isAssignable(
-                typeUtil.erasure(propertyType), mHelper.mCollectionType)) {
-            List<? extends TypeMirror> genericTypes =
-                    ((DeclaredType) propertyType).getTypeArguments();
-            if (genericTypes.isEmpty()) {
-                throw new ProcessingException(
-                        "Property is repeated but has no generic type", property);
-            }
-            propertyType = genericTypes.get(0);
-            repeated = true;
-        } else if (propertyType.getKind() == TypeKind.ARRAY
-                // Byte arrays have a native representation in Icing, so they are not considered a
-                // "repeated" type
-                && !typeUtil.isSameType(propertyType, mHelper.mBytePrimitiveArrayType)
-                && !typeUtil.isSameType(propertyType, mHelper.mByteBoxArrayType)) {
-            propertyType = ((ArrayType) propertyType).getComponentType();
-            repeated = true;
-
-        }
-        ClassName propertyClass;
-        if (typeUtil.isSameType(propertyType, mHelper.mStringType)) {
-            propertyClass = mHelper.getAppSearchClass("AppSearchSchema", "StringPropertyConfig");
-            isPropertyString = true;
-        } else if (typeUtil.isSameType(propertyType, mHelper.mIntegerBoxType)
-                || typeUtil.isSameType(propertyType, mHelper.mIntPrimitiveType)
-                || typeUtil.isSameType(propertyType, mHelper.mLongBoxType)
-                || typeUtil.isSameType(propertyType, mHelper.mLongPrimitiveType)) {
-            propertyClass = mHelper.getAppSearchClass("AppSearchSchema", "LongPropertyConfig");
-            isPropertyLong = true;
-        } else if (typeUtil.isSameType(propertyType, mHelper.mFloatBoxType)
-                || typeUtil.isSameType(propertyType, mHelper.mFloatPrimitiveType)
-                || typeUtil.isSameType(propertyType, mHelper.mDoubleBoxType)
-                || typeUtil.isSameType(propertyType, mHelper.mDoublePrimitiveType)) {
-            propertyClass = mHelper.getAppSearchClass("AppSearchSchema", "DoublePropertyConfig");
-        } else if (typeUtil.isSameType(propertyType, mHelper.mBooleanBoxType)
-                || typeUtil.isSameType(propertyType, mHelper.mBooleanPrimitiveType)) {
-            propertyClass = mHelper.getAppSearchClass("AppSearchSchema", "BooleanPropertyConfig");
-        } else if (typeUtil.isSameType(propertyType, mHelper.mBytePrimitiveArrayType)
-                || typeUtil.isSameType(propertyType, mHelper.mByteBoxArrayType)) {
-            propertyClass = mHelper.getAppSearchClass("AppSearchSchema", "BytesPropertyConfig");
-        } else {
-            propertyClass = mHelper.getAppSearchClass("AppSearchSchema", "DocumentPropertyConfig");
-            isPropertyDocument = true;
-        }
-
-        // Start the builder for the property
-        String propertyName = mModel.getPropertyName(property);
+    /**
+     * Produces an expr for the creating the property's config e.g.
+     *
+     * <pre>
+     * {@code
+     * new StringPropertyConfig.Builder("someProp")
+     *   .setCardinality(StringPropertyConfig.CARDINALITY_REPEATED)
+     *   .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
+     *   .build()
+     * }
+     * </pre>
+     */
+    private CodeBlock createPropertyConfig(
+            @NonNull DataPropertyAnnotation annotation,
+            @NonNull AnnotatedGetterOrField getterOrField) throws ProcessingException {
         CodeBlock.Builder codeBlock = CodeBlock.builder();
-        if (isPropertyDocument) {
-            ClassName documentClass = (ClassName) ClassName.get(propertyType);
-            ClassName documentFactoryClass = mHelper.getDocumentClassFactoryForClass(documentClass);
-            codeBlock.add(
-                    "new $T($S, $T.SCHEMA_NAME)",
-                    propertyClass.nestedClass("Builder"),
-                    propertyName,
+        if (annotation.getDataPropertyKind() == DataPropertyAnnotation.Kind.DOCUMENT_PROPERTY) {
+            ClassName documentClass = (ClassName) ClassName.get(getterOrField.getComponentType());
+            ClassName documentFactoryClass = getDocumentClassFactoryForClass(documentClass);
+            codeBlock.add("new $T.Builder($S, $T.SCHEMA_NAME)",
+                    DocumentPropertyAnnotation.CONFIG_CLASS,
+                    annotation.getName(),
                     documentFactoryClass);
-            mDependencyDocumentClasses.add(documentClass);
         } else {
-            codeBlock.add("new $T($S)", propertyClass.nestedClass("Builder"), propertyName);
+            // All other property configs have a single param constructor that just takes the
+            // property's serialized name as input
+            codeBlock.add("new $T.Builder($S)",
+                    annotation.getConfigClassName(), annotation.getName());
         }
-        codeBlock.indent();
+        codeBlock.indent().add(createSetCardinalityExpr(annotation, getterOrField));
+        switch (annotation.getDataPropertyKind()) {
+            case STRING_PROPERTY:
+                StringPropertyAnnotation stringPropertyAnnotation =
+                        (StringPropertyAnnotation) annotation;
+                codeBlock.add(createSetTokenizerTypeExpr(stringPropertyAnnotation, getterOrField))
+                        .add(createSetIndexingTypeExpr(stringPropertyAnnotation, getterOrField))
+                        .add(createSetJoinableValueTypeExpr(
+                                stringPropertyAnnotation, getterOrField));
+                break;
+            case DOCUMENT_PROPERTY:
+                DocumentPropertyAnnotation documentPropertyAnnotation =
+                        (DocumentPropertyAnnotation) annotation;
+                codeBlock.add(createSetShouldIndexNestedPropertiesExpr(documentPropertyAnnotation));
+                break;
+            case LONG_PROPERTY:
+                LongPropertyAnnotation longPropertyAnnotation = (LongPropertyAnnotation) annotation;
+                codeBlock.add(createSetIndexingTypeExpr(longPropertyAnnotation, getterOrField));
+                break;
+            case DOUBLE_PROPERTY: // fall-through
+            case BOOLEAN_PROPERTY: // fall-through
+            case BYTES_PROPERTY:
+                break;
+            default:
+                throw new IllegalStateException("Unhandled annotation: " + annotation);
+        }
+        return codeBlock.add("\n.build()")
+                .unindent()
+                .build();
+    }
 
-        // Find property cardinality
-        ClassName cardinalityEnum;
-        if (repeated) {
-            cardinalityEnum = mHelper.getAppSearchClass(
-                    "AppSearchSchema", "PropertyConfig", "CARDINALITY_REPEATED");
-        } else if (Boolean.parseBoolean(params.get("required").toString())) {
-            cardinalityEnum = mHelper.getAppSearchClass(
-                    "AppSearchSchema", "PropertyConfig", "CARDINALITY_REQUIRED");
+    /**
+     * Creates an expr like {@code .setCardinality(PropertyConfig.CARDINALITY_REPEATED)}.
+     */
+    @NonNull
+    private static CodeBlock createSetCardinalityExpr(
+            @NonNull DataPropertyAnnotation annotation,
+            @NonNull AnnotatedGetterOrField getterOrField) {
+        AnnotatedGetterOrField.ElementTypeCategory typeCategory =
+                getterOrField.getElementTypeCategory();
+        String enumName;
+        switch (typeCategory) {
+            case COLLECTION: // fall-through
+            case ARRAY:
+                enumName = "CARDINALITY_REPEATED";
+                break;
+            case SINGLE:
+                enumName = annotation.isRequired()
+                        ? "CARDINALITY_REQUIRED"
+                        : "CARDINALITY_OPTIONAL";
+                break;
+            default:
+                throw new IllegalStateException("Unhandled type category: " + typeCategory);
+        }
+        return CodeBlock.of("\n.setCardinality($T.$N)", PROPERTY_CONFIG_CLASS, enumName);
+    }
+
+    /**
+     * Creates an expr like {@code .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)}.
+     */
+    @NonNull
+    private static CodeBlock createSetTokenizerTypeExpr(
+            @NonNull StringPropertyAnnotation annotation,
+            @NonNull AnnotatedGetterOrField getterOrField) throws ProcessingException {
+        String enumName;
+        if (annotation.getIndexingType() == 0) { // INDEXING_TYPE_NONE
+            //TODO(b/171857731) remove this hack after apply to Icing lib's change.
+            enumName = "TOKENIZER_TYPE_NONE";
         } else {
-            cardinalityEnum = mHelper.getAppSearchClass(
-                    "AppSearchSchema", "PropertyConfig", "CARDINALITY_OPTIONAL");
-        }
-        codeBlock.add("\n.setCardinality($T)", cardinalityEnum);
-
-        if (isPropertyString) {
-            // Find tokenizer type
-            int tokenizerType = Integer.parseInt(params.get("tokenizerType").toString());
-            if (Integer.parseInt(params.get("indexingType").toString()) == 0) {
-                //TODO(b/171857731) remove this hack after apply to Icing lib's change.
-                tokenizerType = 0;
-            }
-            ClassName tokenizerEnum;
-            if (tokenizerType == 0) {  // TOKENIZER_TYPE_NONE
-                tokenizerEnum = mHelper.getAppSearchClass(
-                        "AppSearchSchema", "StringPropertyConfig", "TOKENIZER_TYPE_NONE");
-            } else if (tokenizerType == 1) {  // TOKENIZER_TYPE_PLAIN
-                tokenizerEnum = mHelper.getAppSearchClass(
-                        "AppSearchSchema", "StringPropertyConfig", "TOKENIZER_TYPE_PLAIN");
-            } else if (tokenizerType == 2) {  // TOKENIZER_TYPE_VERBATIM
-                tokenizerEnum = mHelper.getAppSearchClass(
-                        "AppSearchSchema", "StringPropertyConfig", "TOKENIZER_TYPE_VERBATIM");
-            } else if (tokenizerType == 3) { // TOKENIZER_TYPE_RFC822
-                tokenizerEnum = mHelper.getAppSearchClass(
-                        "AppSearchSchema", "StringPropertyConfig", "TOKENIZER_TYPE_RFC822");
-            } else {
-                throw new ProcessingException("Unknown tokenizer type " + tokenizerType, property);
-            }
-            codeBlock.add("\n.setTokenizerType($T)", tokenizerEnum);
-
-            // Find indexing type
-            int indexingType = Integer.parseInt(params.get("indexingType").toString());
-            ClassName indexingEnum;
-            if (indexingType == 0) {  // INDEXING_TYPE_NONE
-                indexingEnum = mHelper.getAppSearchClass(
-                        "AppSearchSchema", "StringPropertyConfig", "INDEXING_TYPE_NONE");
-            } else if (indexingType == 1) {  // INDEXING_TYPE_EXACT_TERMS
-                indexingEnum = mHelper.getAppSearchClass(
-                        "AppSearchSchema", "StringPropertyConfig", "INDEXING_TYPE_EXACT_TERMS");
-            } else if (indexingType == 2) {  // INDEXING_TYPE_PREFIXES
-                indexingEnum = mHelper.getAppSearchClass(
-                        "AppSearchSchema", "StringPropertyConfig", "INDEXING_TYPE_PREFIXES");
-            } else {
-                throw new ProcessingException("Unknown indexing type " + indexingType, property);
-            }
-            codeBlock.add("\n.setIndexingType($T)", indexingEnum);
-
-            int joinableValueType = Integer.parseInt(params.get("joinableValueType").toString());
-            ClassName joinableEnum;
-            if (joinableValueType == 0) { // JOINABLE_VALUE_TYPE_NONE
-                joinableEnum = mHelper.getAppSearchClass(
-                        "AppSearchSchema", "StringPropertyConfig", "JOINABLE_VALUE_TYPE_NONE");
-
-            } else if (joinableValueType == 1) { // JOINABLE_VALUE_TYPE_QUALIFIED_ID
-                if (repeated) {
+            switch (annotation.getTokenizerType()) {
+                case 0:
+                    enumName = "TOKENIZER_TYPE_NONE";
+                    break;
+                case 1:
+                    enumName = "TOKENIZER_TYPE_PLAIN";
+                    break;
+                case 2:
+                    enumName = "TOKENIZER_TYPE_VERBATIM";
+                    break;
+                case 3:
+                    enumName = "TOKENIZER_TYPE_RFC822";
+                    break;
+                default:
                     throw new ProcessingException(
-                            "Joinable value type " + joinableValueType + " not allowed on repeated "
-                                    + "properties.", property);
-
-                }
-                joinableEnum = mHelper.getAppSearchClass(
-                        "AppSearchSchema", "StringPropertyConfig",
-                        "JOINABLE_VALUE_TYPE_QUALIFIED_ID");
-            } else {
-                throw new ProcessingException(
-                        "Unknown joinable value type " + joinableValueType, property);
+                            "Unknown tokenizer type " + annotation.getTokenizerType(),
+                            getterOrField.getElement());
             }
-            codeBlock.add("\n.setJoinableValueType($T)", joinableEnum);
-
-        } else if (isPropertyDocument) {
-            if (params.containsKey("indexNestedProperties")) {
-                boolean indexNestedProperties = Boolean.parseBoolean(
-                        params.get("indexNestedProperties").toString());
-
-                codeBlock.add("\n.setShouldIndexNestedProperties($L)", indexNestedProperties);
-            }
-        } else if (isPropertyLong) {
-            int indexingType = 0;  // INDEXING_TYPE_NONE
-            if (params.containsKey("indexingType")) {
-                indexingType = Integer.parseInt(params.get("indexingType").toString());
-            }
-
-            ClassName indexingEnum;
-            if (indexingType == 0) {  // INDEXING_TYPE_NONE
-                indexingEnum = mHelper.getAppSearchClass(
-                        "AppSearchSchema", "LongPropertyConfig", "INDEXING_TYPE_NONE");
-            } else if (indexingType == 1) {  // INDEXING_TYPE_RANGE
-                indexingEnum = mHelper.getAppSearchClass(
-                        "AppSearchSchema", "LongPropertyConfig", "INDEXING_TYPE_RANGE");
-            } else {
-                throw new ProcessingException("Unknown indexing type " + indexingType, property);
-            }
-            codeBlock.add("\n.setIndexingType($T)", indexingEnum);
         }
+        return CodeBlock.of("\n.setTokenizerType($T.$N)",
+                StringPropertyAnnotation.CONFIG_CLASS, enumName);
+    }
 
-        // Done!
-        codeBlock.add("\n.build()");
-        codeBlock.unindent();
-        return codeBlock.build();
+    /**
+     * Creates an expr like {@code .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES)}.
+     */
+    @NonNull
+    private static CodeBlock createSetIndexingTypeExpr(
+            @NonNull StringPropertyAnnotation annotation,
+            @NonNull AnnotatedGetterOrField getterOrField) throws ProcessingException {
+        String enumName;
+        switch (annotation.getIndexingType()) {
+            case 0:
+                enumName = "INDEXING_TYPE_NONE";
+                break;
+            case 1:
+                enumName = "INDEXING_TYPE_EXACT_TERMS";
+                break;
+            case 2:
+                enumName = "INDEXING_TYPE_PREFIXES";
+                break;
+            default:
+                throw new ProcessingException(
+                        "Unknown indexing type " + annotation.getIndexingType(),
+                        getterOrField.getElement());
+        }
+        return CodeBlock.of("\n.setIndexingType($T.$N)",
+                StringPropertyAnnotation.CONFIG_CLASS, enumName);
+    }
+
+    /**
+     * Creates an expr like {@code .setShouldIndexNestedProperties(true)}.
+     */
+    @NonNull
+    private static CodeBlock createSetShouldIndexNestedPropertiesExpr(
+            @NonNull DocumentPropertyAnnotation annotation) {
+        return CodeBlock.of("\n.setShouldIndexNestedProperties($L)",
+                annotation.shouldIndexNestedProperties());
+    }
+
+    /**
+     * Creates an expr like {@code .setIndexingType(LongPropertyConfig.INDEXING_TYPE_RANGE)}.
+     */
+    @NonNull
+    private static CodeBlock createSetIndexingTypeExpr(
+            @NonNull LongPropertyAnnotation annotation,
+            @NonNull AnnotatedGetterOrField getterOrField) throws ProcessingException {
+        String enumName;
+        switch (annotation.getIndexingType()) {
+            case 0:
+                enumName = "INDEXING_TYPE_NONE";
+                break;
+            case 1:
+                enumName = "INDEXING_TYPE_RANGE";
+                break;
+            default:
+                throw new ProcessingException(
+                        "Unknown indexing type " + annotation.getIndexingType(),
+                        getterOrField.getElement());
+        }
+        return CodeBlock.of("\n.setIndexingType($T.$N)",
+                LongPropertyAnnotation.CONFIG_CLASS, enumName);
+    }
+
+    /**
+     * Creates an expr like
+     * {@code .setJoinableValueType(StringPropertyConfig.JOINABLE_VALUE_TYPE_QUALIFIED_ID)}.
+     */
+    @NonNull
+    private static CodeBlock createSetJoinableValueTypeExpr(
+            @NonNull StringPropertyAnnotation annotation,
+            @NonNull AnnotatedGetterOrField getterOrField) throws ProcessingException {
+        String enumName;
+        AnnotatedGetterOrField.ElementTypeCategory typeCategory =
+                getterOrField.getElementTypeCategory();
+        switch (annotation.getJoinableValueType()) {
+            case 0:
+                enumName = "JOINABLE_VALUE_TYPE_NONE";
+                break;
+            case 1:
+                switch (typeCategory) {
+                    case COLLECTION: // fall-through
+                    case ARRAY:
+                        throw new ProcessingException(
+                                "Joinable value type 1 not allowed on repeated properties.",
+                                getterOrField.getElement());
+                    case SINGLE: // fall-through
+                        break;
+                    default:
+                        throw new IllegalStateException("Unhandled cardinality: " + typeCategory);
+                }
+                enumName = "JOINABLE_VALUE_TYPE_QUALIFIED_ID";
+                break;
+            default:
+                throw new ProcessingException(
+                        "Unknown joinable value type " + annotation.getJoinableValueType(),
+                        getterOrField.getElement());
+        }
+        return CodeBlock.of("\n.setJoinableValueType($T.$N)",
+                StringPropertyAnnotation.CONFIG_CLASS, enumName);
     }
 }
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/ToGenericDocumentCodeGenerator.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/ToGenericDocumentCodeGenerator.java
index 2f82106..9aa28a2 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/ToGenericDocumentCodeGenerator.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/ToGenericDocumentCodeGenerator.java
@@ -16,30 +16,29 @@
 
 package androidx.appsearch.compiler;
 
-import static androidx.appsearch.compiler.IntrospectionHelper.getDocumentAnnotation;
-import static androidx.appsearch.compiler.IntrospectionHelper.getPropertyType;
+import static androidx.appsearch.compiler.IntrospectionHelper.APPSEARCH_EXCEPTION_CLASS;
+import static androidx.appsearch.compiler.IntrospectionHelper.GENERIC_DOCUMENT_CLASS;
 
 import androidx.annotation.NonNull;
+import androidx.appsearch.compiler.AnnotatedGetterOrField.ElementTypeCategory;
+import androidx.appsearch.compiler.annotationwrapper.DataPropertyAnnotation;
+import androidx.appsearch.compiler.annotationwrapper.DocumentPropertyAnnotation;
+import androidx.appsearch.compiler.annotationwrapper.MetadataPropertyAnnotation;
+import androidx.appsearch.compiler.annotationwrapper.PropertyAnnotation;
 
+import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.CodeBlock;
 import com.squareup.javapoet.MethodSpec;
 import com.squareup.javapoet.ParameterizedTypeName;
-import com.squareup.javapoet.TypeName;
 import com.squareup.javapoet.TypeSpec;
 import com.squareup.javapoet.WildcardTypeName;
 
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-
 import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.Element;
 import javax.lang.model.element.Modifier;
 import javax.lang.model.type.ArrayType;
-import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.PrimitiveType;
 import javax.lang.model.type.TypeKind;
 import javax.lang.model.type.TypeMirror;
-import javax.lang.model.util.Types;
 
 /**
  * Generates java code for a translator from an instance of a class annotated with
@@ -47,13 +46,11 @@
  * {@link androidx.appsearch.app.GenericDocument}.
  */
 class ToGenericDocumentCodeGenerator {
-    private final ProcessingEnvironment mEnv;
     private final IntrospectionHelper mHelper;
     private final DocumentModel mModel;
 
     private ToGenericDocumentCodeGenerator(
             @NonNull ProcessingEnvironment env, @NonNull DocumentModel model) {
-        mEnv = env;
         mHelper = new IntrospectionHelper(env);
         mModel = model;
     }
@@ -61,40 +58,54 @@
     public static void generate(
             @NonNull ProcessingEnvironment env,
             @NonNull DocumentModel model,
-            @NonNull TypeSpec.Builder classBuilder) throws ProcessingException {
+            @NonNull TypeSpec.Builder classBuilder) {
         new ToGenericDocumentCodeGenerator(env, model).generate(classBuilder);
     }
 
-    private void generate(TypeSpec.Builder classBuilder) throws ProcessingException {
+    private void generate(TypeSpec.Builder classBuilder) {
         classBuilder.addMethod(createToGenericDocumentMethod());
     }
 
-    private MethodSpec createToGenericDocumentMethod() throws ProcessingException {
+    private MethodSpec createToGenericDocumentMethod() {
         // Method header
-        TypeName classType = TypeName.get(mModel.getClassElement().asType());
         MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("toGenericDocument")
                 .addModifiers(Modifier.PUBLIC)
-                .returns(mHelper.getAppSearchClass("GenericDocument"))
+                .returns(GENERIC_DOCUMENT_CLASS)
                 .addAnnotation(Override.class)
-                .addParameter(classType, "document")
-                .addException(mHelper.getAppSearchExceptionClass());
+                .addParameter(ClassName.get(mModel.getClassElement()), "document")
+                .addException(APPSEARCH_EXCEPTION_CLASS);
 
         // Construct a new GenericDocument.Builder with the namespace, id, and schema type
         methodBuilder.addStatement("$T builder =\nnew $T<>($L, $L, SCHEMA_NAME)",
                 ParameterizedTypeName.get(
-                        mHelper.getAppSearchClass("GenericDocument", "Builder"),
+                        GENERIC_DOCUMENT_CLASS.nestedClass("Builder"),
                         WildcardTypeName.subtypeOf(Object.class)),
-                mHelper.getAppSearchClass("GenericDocument", "Builder"),
-                createAppSearchFieldRead(
-                        mModel.getSpecialFieldName(DocumentModel.SpecialField.NAMESPACE)),
-                createAppSearchFieldRead(
-                        mModel.getSpecialFieldName(DocumentModel.SpecialField.ID)));
+                GENERIC_DOCUMENT_CLASS.nestedClass("Builder"),
+                createReadExpr(mModel.getNamespaceAnnotatedGetterOrField()),
+                createReadExpr(mModel.getIdAnnotatedGetterOrField()));
 
-        setSpecialFields(methodBuilder);
+        // Set metadata properties
+        for (AnnotatedGetterOrField getterOrField : mModel.getAnnotatedGettersAndFields()) {
+            PropertyAnnotation annotation = getterOrField.getAnnotation();
+            if (annotation.getPropertyKind() != PropertyAnnotation.Kind.METADATA_PROPERTY
+                    // Already set in the generated constructor above
+                    || annotation == MetadataPropertyAnnotation.ID
+                    || annotation == MetadataPropertyAnnotation.NAMESPACE) {
+                continue;
+            }
 
-        // Set properties
-        for (Map.Entry<String, Element> entry : mModel.getPropertyElements().entrySet()) {
-            fieldToGenericDoc(methodBuilder, entry.getKey(), entry.getValue());
+            methodBuilder.addCode(codeToCopyIntoGenericDoc(
+                    (MetadataPropertyAnnotation) annotation, getterOrField));
+        }
+
+        // Set data properties
+        for (AnnotatedGetterOrField getterOrField : mModel.getAnnotatedGettersAndFields()) {
+            PropertyAnnotation annotation = getterOrField.getAnnotation();
+            if (annotation.getPropertyKind() != PropertyAnnotation.Kind.DATA_PROPERTY) {
+                continue;
+            }
+            methodBuilder.addCode(codeToCopyIntoGenericDoc(
+                    (DataPropertyAnnotation) annotation, getterOrField));
         }
 
         methodBuilder.addStatement("return builder.build()");
@@ -102,19 +113,52 @@
     }
 
     /**
-     * Converts a field from a document class into a format suitable for one of the
-     * {@link androidx.appsearch.app.GenericDocument.Builder#setProperty} methods.
+     * Returns code that copies the getter/field annotated with a {@link MetadataPropertyAnnotation}
+     * from a document class into a {@code GenericDocument.Builder}.
+     *
+     * <p>Assumes:
+     * <ol>
+     *     <li>There is a document class var in-scope called {@code document}.</li>
+     *     <li>There is {@code GenericDocument.Builder} var in-scope called {@code builder}.</li>
+     *     <li>
+     *         The annotation is not {@link MetadataPropertyAnnotation#ID} or
+     *         {@link MetadataPropertyAnnotation#NAMESPACE}.
+     *     </li>
+     * </ol>
      */
-    private void fieldToGenericDoc(
-            @NonNull MethodSpec.Builder method,
-            @NonNull String fieldName,
-            @NonNull Element property) throws ProcessingException {
+    private CodeBlock codeToCopyIntoGenericDoc(
+            @NonNull MetadataPropertyAnnotation annotation,
+            @NonNull AnnotatedGetterOrField getterOrField) {
+        if (getterOrField.getJvmType() instanceof PrimitiveType) {
+            // Directly set it
+            return CodeBlock.builder()
+                    .addStatement("builder.$N($L)",
+                            annotation.getGenericDocSetterName(), createReadExpr(getterOrField))
+                    .build();
+        }
+        // Boxed type. Need to guard against the case where the value is null at runtime.
+        return fieldUseDirectlyWithNullCheck(annotation, getterOrField);
+    }
+
+    /**
+     * Returns code that copies the getter/field annotated with a {@link DataPropertyAnnotation}
+     * from a document class into a {@code GenericDocument.Builder}.
+     *
+     * <p>Assumes:
+     * <ol>
+     *     <li>There is a document class var in-scope called {@code document}.</li>
+     *     <li>There is {@code GenericDocument.Builder} var in-scope called {@code builder}.</li>
+     * </ol>
+     */
+    private CodeBlock codeToCopyIntoGenericDoc(
+            @NonNull DataPropertyAnnotation annotation,
+            @NonNull AnnotatedGetterOrField getterOrField) {
         // Scenario 1: field is a Collection
         //   1a: CollectionForLoopAssign
-        //       Collection contains boxed Long, Integer, Double, Float, Boolean or byte[].
-        //       We have to pack it into a primitive array of type long[], double[], boolean[],
-        //       or byte[][] by reading each element one-by-one and assigning it. The compiler takes
-        //       care of unboxing.
+        //       Collection contains boxed Long, Integer, Double, Float, Boolean, byte[].
+        //       We have to pack it into a primitive array of type long[], double[], boolean[] or
+        //       byte[][] by reading each element one-by-one and assigning it. The compiler takes
+        //       care of unboxing and widening where necessary.
         //
         //   1b: CollectionCallToArray
         //       Collection contains String or GenericDocument.
@@ -126,20 +170,13 @@
         //       Collection contains a class which is annotated with @Document.
         //       We have to convert this into an array of GenericDocument[], by reading each element
         //       one-by-one and converting it through the standard conversion machinery.
-        //
-        //   1x: Collection contains any other kind of class. This unsupported and compilation
-        //       fails.
-        //       Note: Set<Byte[]>, Set<Byte>, and Set<Set<Byte>> are in this category. We don't
-        //       support such conversions currently, but in principle they are possible and could
-        //       be implemented.
 
         // Scenario 2: field is an Array
         //   2a: ArrayForLoopAssign
-        //       Array is of type Long[], Integer[], int[], Double[], Float[], float[], Boolean[],
-        //       or Byte[].
-        //       We have to pack it into a primitive array of type long[], double[], boolean[] or
-        //       byte[] by reading each element one-by-one and assigning it. The compiler takes care
-        //       of unboxing.
+        //       Array is of type Long[], Integer[], int[], Double[], Float[], float[], Boolean[].
+        //       We have to pack it into a primitive array of type long[], double[], boolean[]
+        //       by reading each element one-by-one and assigning it. The compiler takes care of
+        //       unboxing and widening where necessary.
         //
         //   2b: ArrayUseDirectly
         //       Array is of type String[], long[], double[], boolean[], byte[][] or
@@ -153,15 +190,10 @@
         //
         //   2d: Array is of class byte[]. This is actually a single-valued field as byte arrays are
         //       natively supported by Icing, and is handled as Scenario 3a.
-        //
-        //   2x: Array is of any other kind of class. This unsupported and compilation fails.
-        //       Note: Byte[][] is in this category. We don't support such conversions
-        //       currently, but in principle they are possible and could be implemented.
 
         // Scenario 3: Single valued fields
         //   3a: FieldUseDirectlyWithNullCheck
-        //       Field is of type String, Long, Integer, Double, Float, Boolean, byte[] or
-        //       GenericDocument.
+        //       Field is of type String, Long, Integer, Double, Float, Boolean.
         //       We can use this field directly, after testing for null. The java compiler will box
         //       or unbox as needed.
         //
@@ -173,553 +205,405 @@
         //       Field is of a class which is annotated with @Document.
         //       We have to convert this into a GenericDocument through the standard conversion
         //       machinery.
-        String propertyName = mModel.getPropertyName(property);
-        if (tryConvertFromCollection(method, fieldName, propertyName, property)) {
-            return;
+        ElementTypeCategory typeCategory = getterOrField.getElementTypeCategory();
+        switch (annotation.getDataPropertyKind()) {
+            case STRING_PROPERTY:
+                switch (typeCategory) {
+                    case COLLECTION: // List<String>: 1b
+                        return collectionCallToArray(annotation, getterOrField);
+                    case ARRAY: // String[]: 2b
+                        return arrayUseDirectly(annotation, getterOrField);
+                    case SINGLE: // String: 3a
+                        return fieldUseDirectlyWithNullCheck(annotation, getterOrField);
+                    default:
+                        throw new IllegalStateException("Unhandled type-category: " + typeCategory);
+                }
+            case DOCUMENT_PROPERTY:
+                DocumentPropertyAnnotation docPropAnnotation =
+                        (DocumentPropertyAnnotation) annotation;
+                switch (typeCategory) {
+                    case COLLECTION: // List<Person>: 1c
+                        return collectionForLoopCallToGenericDocument(
+                                docPropAnnotation, getterOrField);
+                    case ARRAY: // Person[]: 2c
+                        return arrayForLoopCallToGenericDocument(docPropAnnotation, getterOrField);
+                    case SINGLE: // Person: 3c
+                        return fieldCallToGenericDocument(docPropAnnotation, getterOrField);
+                    default:
+                        throw new IllegalStateException("Unhandled type-category: " + typeCategory);
+                }
+            case LONG_PROPERTY:
+                switch (typeCategory) {
+                    case COLLECTION: // List<Long>|List<Integer>: 1a
+                        return collectionForLoopAssign(
+                                annotation,
+                                getterOrField,
+                                /* targetArrayComponentType= */mHelper.mLongPrimitiveType);
+                    case ARRAY:
+                        if (mHelper.isPrimitiveLongArray(getterOrField.getJvmType())) {
+                            return arrayUseDirectly(annotation, getterOrField); // long[]: 2b
+                        } else {
+                            // Long[]|Integer[]|int[]: 2a
+                            return arrayForLoopAssign(
+                                    annotation,
+                                    getterOrField,
+                                    /* targetArrayComponentType= */mHelper.mLongPrimitiveType);
+                        }
+                    case SINGLE:
+                        if (getterOrField.getJvmType() instanceof PrimitiveType) {
+                            // long|int: 3b
+                            return fieldUseDirectlyWithoutNullCheck(annotation, getterOrField);
+                        } else {
+                            // Long|Integer: 3a
+                            return fieldUseDirectlyWithNullCheck(annotation, getterOrField);
+                        }
+                    default:
+                        throw new IllegalStateException("Unhandled type-category: " + typeCategory);
+                }
+            case DOUBLE_PROPERTY:
+                switch (typeCategory) {
+                    case COLLECTION: // List<Double>|List<Float>: 1a
+                        return collectionForLoopAssign(
+                                annotation,
+                                getterOrField,
+                                /* targetArrayComponentType= */mHelper.mDoublePrimitiveType);
+                    case ARRAY:
+                        if (mHelper.isPrimitiveDoubleArray(getterOrField.getJvmType())) {
+                            return arrayUseDirectly(annotation, getterOrField); // double[]: 2b
+                        } else {
+                            // Double[]|Float[]|float[]: 2a
+                            return arrayForLoopAssign(
+                                    annotation,
+                                    getterOrField,
+                                    /* targetArrayComponentType= */mHelper.mDoublePrimitiveType);
+                        }
+                    case SINGLE:
+                        if (getterOrField.getJvmType() instanceof PrimitiveType) {
+                            // double|float: 3b
+                            return fieldUseDirectlyWithoutNullCheck(annotation, getterOrField);
+                        } else {
+                            // Double|Float: 3b
+                            return fieldUseDirectlyWithNullCheck(annotation, getterOrField);
+                        }
+                    default:
+                        throw new IllegalStateException("Unhandled type-category: " + typeCategory);
+                }
+            case BOOLEAN_PROPERTY:
+                switch (typeCategory) {
+                    case COLLECTION: // List<Boolean>: 1a
+                        return collectionForLoopAssign(
+                                annotation,
+                                getterOrField,
+                                /* targetArrayComponentType= */mHelper.mBooleanPrimitiveType);
+                    case ARRAY:
+                        if (mHelper.isPrimitiveBooleanArray(getterOrField.getJvmType())) {
+                            return arrayUseDirectly(annotation, getterOrField); // boolean[]: 2b
+                        } else {
+                            // Boolean[]: 2a
+                            return arrayForLoopAssign(
+                                    annotation,
+                                    getterOrField,
+                                    /* targetArrayComponentType= */mHelper.mBooleanPrimitiveType);
+                        }
+                    case SINGLE:
+                        if (getterOrField.getJvmType() instanceof PrimitiveType) {
+                            // boolean: 3b
+                            return fieldUseDirectlyWithoutNullCheck(annotation, getterOrField);
+                        } else {
+                            // Boolean: 3a
+                            return fieldUseDirectlyWithNullCheck(annotation, getterOrField);
+                        }
+                    default:
+                        throw new IllegalStateException("Unhandled type-category: " + typeCategory);
+                }
+            case BYTES_PROPERTY:
+                switch (typeCategory) {
+                    case COLLECTION: // List<byte[]>: 1a
+                        return collectionForLoopAssign(
+                                annotation,
+                                getterOrField,
+                                /* targetArrayComponentType= */mHelper.mBytePrimitiveArrayType);
+                    case ARRAY: // byte[][]: 2b
+                        return arrayUseDirectly(annotation, getterOrField);
+                    case SINGLE: // byte[]: 2d
+                        return fieldUseDirectlyWithNullCheck(annotation, getterOrField);
+                    default:
+                        throw new IllegalStateException("Unhandled type-category: " + typeCategory);
+                }
+            default:
+                throw new IllegalStateException("Unhandled annotation: " + annotation);
         }
-        if (tryConvertFromArray(method, fieldName, propertyName, property)) {
-            return;
+    }
+
+    // 1a: CollectionForLoopAssign
+    //     Collection contains boxed Long, Integer, Double, Float, Boolean, byte[].
+    //     We have to pack it into a primitive array of type long[], double[], boolean[] or
+    //     byte[][] by reading each element one-by-one and assigning it. The compiler takes
+    //     care of unboxing and widening where necessary.
+    @NonNull
+    private CodeBlock collectionForLoopAssign(
+            @NonNull DataPropertyAnnotation annotation,
+            @NonNull AnnotatedGetterOrField getterOrField,
+            @NonNull TypeMirror targetArrayComponentType) {
+        TypeMirror jvmType = getterOrField.getJvmType(); // e.g. List<Long>
+        TypeMirror componentType = getterOrField.getComponentType(); // e.g. Long
+        String jvmName = getterOrField.getJvmName(); // e.g. mProp|getProp
+        return CodeBlock.builder()
+                .addStatement("$T $NCopy = $L",
+                        jvmType, jvmName, createReadExpr(getterOrField))
+                .beginControlFlow("if ($NCopy != null)", jvmName)
+                .addStatement("$T[] $NConv = $L",
+                        targetArrayComponentType,
+                        jvmName,
+                        newArrayExpr(
+                                targetArrayComponentType,
+                                /* size= */CodeBlock.of("$NCopy.size()", jvmName)))
+                .addStatement("int i = 0")
+                .beginControlFlow("for ($T item : $NCopy)", componentType, jvmName)
+                .addStatement("$NConv[i++] = item", jvmName)
+                .endControlFlow() // for (...) {
+                .addStatement("builder.$N($S, $NConv)",
+                        getterOrField.getAnnotation().getGenericDocSetterName(),
+                        annotation.getName(),
+                        jvmName)
+                .endControlFlow() //  if ($NCopy != null) {
+                .build();
+    }
+
+    // 1b: CollectionCallToArray
+    //     Collection contains String or GenericDocument.
+    //     We have to convert this into an array of String[] or GenericDocument[], but no
+    //     conversion of the collection elements is needed. We can use Collection#toArray for
+    //     this.
+    @NonNull
+    private CodeBlock collectionCallToArray(
+            @NonNull DataPropertyAnnotation annotation,
+            @NonNull AnnotatedGetterOrField getterOrField) {
+        TypeMirror collectionType = getterOrField.getJvmType(); // e.g. List<String>
+        TypeMirror componentType = getterOrField.getComponentType(); // e.g. String
+        String jvmName = getterOrField.getJvmName(); // e.g. mProp|getProp
+        return CodeBlock.builder()
+                .addStatement("$T $NCopy = $L",
+                        collectionType, jvmName, createReadExpr(getterOrField))
+                .beginControlFlow("if ($NCopy != null)", jvmName)
+                .addStatement("$T[] $NConv = $NCopy.toArray(new $T[0])",
+                        componentType, jvmName, jvmName, componentType)
+                .addStatement("builder.$N($S, $NConv)",
+                        getterOrField.getAnnotation().getGenericDocSetterName(),
+                        annotation.getName(),
+                        jvmName)
+                .endControlFlow() //  if ($NCopy != null) {
+                .build();
+    }
+
+    // 1c: CollectionForLoopCallToGenericDocument
+    //     Collection contains a class which is annotated with @Document.
+    //     We have to convert this into an array of GenericDocument[], by reading each element
+    //     one-by-one and converting it through the standard conversion machinery.
+    @NonNull
+    private CodeBlock collectionForLoopCallToGenericDocument(
+            @NonNull DocumentPropertyAnnotation annotation,
+            @NonNull AnnotatedGetterOrField getterOrField) {
+        TypeMirror collectionType = getterOrField.getJvmType(); // e.g. List<Person>
+        TypeMirror documentClass = getterOrField.getComponentType(); // e.g. Person
+        String jvmName = getterOrField.getJvmName(); // e.g. mProp|getProp
+        return CodeBlock.builder()
+                .addStatement("$T $NCopy = $L",
+                        collectionType, jvmName, createReadExpr(getterOrField))
+                .beginControlFlow("if ($NCopy != null)", jvmName)
+                .addStatement("$T[] $NConv = new $T[$NCopy.size()]",
+                        GENERIC_DOCUMENT_CLASS, jvmName, GENERIC_DOCUMENT_CLASS, jvmName)
+                .addStatement("int i = 0")
+                .beginControlFlow("for ($T item : $NCopy)", documentClass, jvmName)
+                .addStatement("$NConv[i++] = $T.fromDocumentClass(item)",
+                        jvmName, GENERIC_DOCUMENT_CLASS)
+                .endControlFlow() // for (...) {
+                .addStatement("builder.setPropertyDocument($S, $NConv)",
+                        annotation.getName(), jvmName)
+                .endControlFlow() //  if ($NCopy != null) {
+                .build();
+    }
+
+    // 2a: ArrayForLoopAssign
+    //     Array is of type Long[], Integer[], int[], Double[], Float[], float[], Boolean[].
+    //     We have to pack it into a primitive array of type long[], double[], boolean[]
+    //     by reading each element one-by-one and assigning it. The compiler takes care of
+    //     unboxing and widening where necessary.
+    @NonNull
+    private CodeBlock arrayForLoopAssign(
+            @NonNull DataPropertyAnnotation annotation,
+            @NonNull AnnotatedGetterOrField getterOrField,
+            @NonNull TypeMirror targetArrayComponentType) {
+        TypeMirror jvmType = getterOrField.getJvmType(); // e.g. Long[]
+        String jvmName = getterOrField.getJvmName(); // e.g. mProp|getProp
+        return CodeBlock.builder()
+                .addStatement("$T $NCopy = $L",
+                        jvmType, jvmName, createReadExpr(getterOrField))
+                .beginControlFlow("if ($NCopy != null)", jvmName)
+                .addStatement("$T[] $NConv = $L",
+                        targetArrayComponentType,
+                        jvmName,
+                        newArrayExpr(
+                                targetArrayComponentType,
+                                /* size= */CodeBlock.of("$NCopy.length", jvmName)))
+                .beginControlFlow("for (int i = 0; i < $NCopy.length; i++)", jvmName)
+                .addStatement("$NConv[i] = $NCopy[i]", jvmName, jvmName)
+                .endControlFlow() // for (...) {
+                .addStatement("builder.$N($S, $NConv)",
+                        getterOrField.getAnnotation().getGenericDocSetterName(),
+                        annotation.getName(),
+                        jvmName)
+                .endControlFlow() // if ($NCopy != null)
+                .build();
+    }
+
+    // 2b: ArrayUseDirectly
+    //     Array is of type String[], long[], double[], boolean[], byte[][] or
+    //     GenericDocument[].
+    //     We can directly use this field with no conversion.
+    @NonNull
+    private CodeBlock arrayUseDirectly(
+            @NonNull DataPropertyAnnotation annotation,
+            @NonNull AnnotatedGetterOrField getterOrField) {
+        TypeMirror jvmType = getterOrField.getJvmType(); // e.g. String[]
+        String jvmName = getterOrField.getJvmName(); // e.g. mProp|getProp
+        return CodeBlock.builder()
+                .addStatement("$T $NCopy = $L",
+                        jvmType, jvmName, createReadExpr(getterOrField))
+                .beginControlFlow("if ($NCopy != null)", jvmName)
+                .addStatement("builder.$N($S, $NCopy)",
+                        getterOrField.getAnnotation().getGenericDocSetterName(),
+                        annotation.getName(),
+                        jvmName)
+                .endControlFlow() // if ($NCopy != null)
+                .build();
+    }
+
+    // 2c: ArrayForLoopCallToGenericDocument
+    //     Array is of a class which is annotated with @Document.
+    //     We have to convert this into an array of GenericDocument[], by reading each element
+    //     one-by-one and converting it through the standard conversion machinery.
+    @NonNull
+    private CodeBlock arrayForLoopCallToGenericDocument(
+            @NonNull DocumentPropertyAnnotation annotation,
+            @NonNull AnnotatedGetterOrField getterOrField) {
+        TypeMirror jvmType = getterOrField.getJvmType(); // e.g. Person[]
+        String jvmName = getterOrField.getJvmName(); // e.g. mProp|getProp
+        return CodeBlock.builder()
+                .addStatement("$T $NCopy = $L",
+                        jvmType, jvmName, createReadExpr(getterOrField))
+                .beginControlFlow("if ($NCopy != null)", jvmName)
+                .addStatement("$T[] $NConv = new $T[$NCopy.length]",
+                        GENERIC_DOCUMENT_CLASS, jvmName, GENERIC_DOCUMENT_CLASS, jvmName)
+                .beginControlFlow("for (int i = 0; i < $NConv.length; i++)", jvmName)
+                .addStatement("$NConv[i] = $T.fromDocumentClass($NCopy[i])",
+                        jvmName, GENERIC_DOCUMENT_CLASS, jvmName)
+                .endControlFlow() // for (...) {
+                .addStatement("builder.setPropertyDocument($S, $NConv)",
+                        annotation.getName(), jvmName)
+                .endControlFlow() //  if ($NCopy != null) {
+                .build();
+    }
+
+    // 3a: FieldUseDirectlyWithNullCheck
+    //     Field is of type String, Long, Integer, Double, Float, Boolean.
+    //     We can use this field directly, after testing for null. The java compiler will box
+    //     or unbox as needed.
+    @NonNull
+    private CodeBlock fieldUseDirectlyWithNullCheck(
+            @NonNull PropertyAnnotation annotation,
+            @NonNull AnnotatedGetterOrField getterOrField) {
+        TypeMirror jvmType = getterOrField.getJvmType();
+        String jvmName = getterOrField.getJvmName(); // e.g. mProp|getProp
+        CodeBlock.Builder codeBlock = CodeBlock.builder()
+                .addStatement("$T $NCopy = $L",
+                        jvmType, jvmName, createReadExpr(getterOrField))
+                .beginControlFlow("if ($NCopy != null)", jvmName);
+
+        switch (annotation.getPropertyKind()) {
+            case METADATA_PROPERTY:
+                codeBlock.addStatement("builder.$N($NCopy)",
+                        getterOrField.getAnnotation().getGenericDocSetterName(), jvmName);
+                break;
+            case DATA_PROPERTY:
+                codeBlock.addStatement("builder.$N($S, $NCopy)",
+                        getterOrField.getAnnotation().getGenericDocSetterName(),
+                        ((DataPropertyAnnotation) annotation).getName(),
+                        jvmName);
+                break;
+            default:
+                throw new IllegalStateException("Unhandled annotation: " + annotation);
         }
-        convertFromField(method, fieldName, propertyName, property);
+
+        return codeBlock.endControlFlow() // if ($NCopy != null)
+                .build();
+    }
+
+    // 3b: FieldUseDirectlyWithoutNullCheck
+    //     Field is of type long, int, double, float, or boolean.
+    //     We can use this field directly without testing for null.
+    @NonNull
+    private CodeBlock fieldUseDirectlyWithoutNullCheck(
+            @NonNull DataPropertyAnnotation annotation,
+            @NonNull AnnotatedGetterOrField getterOrField) {
+        return CodeBlock.builder()
+                .addStatement("builder.$N($S, $L)",
+                        getterOrField.getAnnotation().getGenericDocSetterName(),
+                        annotation.getName(),
+                        createReadExpr(getterOrField))
+                .build();
+    }
+
+    // 3c: FieldCallToGenericDocument
+    //     Field is of a class which is annotated with @Document.
+    //     We have to convert this into a GenericDocument through the standard conversion
+    //     machinery.
+    @NonNull
+    private CodeBlock fieldCallToGenericDocument(
+            @NonNull DocumentPropertyAnnotation annotation,
+            @NonNull AnnotatedGetterOrField getterOrField) {
+        TypeMirror documentClass = getterOrField.getJvmType(); // Person
+        String jvmName = getterOrField.getJvmName(); // e.g. mProp|getProp
+        return CodeBlock.builder()
+                .addStatement("$T $NCopy = $L",
+                        documentClass, jvmName, createReadExpr(getterOrField))
+                .beginControlFlow("if ($NCopy != null)", jvmName)
+                .addStatement("$T $NConv = $T.fromDocumentClass($NCopy)",
+                        GENERIC_DOCUMENT_CLASS, jvmName, GENERIC_DOCUMENT_CLASS, jvmName)
+                .addStatement("builder.setPropertyDocument($S, $NConv)",
+                        annotation.getName(), jvmName)
+                .endControlFlow() // if ($NCopy != null) {
+                .build();
+    }
+
+    private CodeBlock newArrayExpr(@NonNull TypeMirror componentType, @NonNull CodeBlock size) {
+        // Component type itself may be an array type e.g. byte[]
+        // Deduce the base component type e.g. byte
+        TypeMirror baseComponentType = componentType;
+        int dims = 1;
+        while (baseComponentType.getKind() == TypeKind.ARRAY) {
+            baseComponentType = ((ArrayType) baseComponentType).getComponentType();
+            dims++;
+        }
+        CodeBlock.Builder codeBlock = CodeBlock.builder()
+                .add("new $T[$L]", baseComponentType, size);
+        for (int i = 1; i < dims; i++) {
+            codeBlock.add("[]");
+        }
+        return codeBlock.build();
     }
 
     /**
-     * If the given field is a Collection, generates code to read it and convert it into a form
-     * suitable for GenericDocument and returns true. If the field is not a Collection, returns
-     * false.
+     * Returns an expr that reading the annotated getter/fields from a document class var.
+     *
+     * <p>Assumes there is a document class var in-scope called {@code document}.
      */
-    private boolean tryConvertFromCollection(
-            @NonNull MethodSpec.Builder method,
-            @NonNull String fieldName,
-            @NonNull String propertyName,
-            @NonNull Element property) throws ProcessingException {
-        Types typeUtil = mEnv.getTypeUtils();
-        TypeMirror propertyType = getPropertyType(property);
-        if (!typeUtil.isAssignable(typeUtil.erasure(propertyType), mHelper.mCollectionType)) {
-            return false;  // This is not a scenario 1 collection
+    private CodeBlock createReadExpr(@NonNull AnnotatedGetterOrField annotatedGetterOrField) {
+        PropertyAccessor accessor = mModel.getAccessor(annotatedGetterOrField);
+        if (accessor.isField()) {
+            return CodeBlock.of("document.$N", accessor.getElement().getSimpleName().toString());
+        } else { // getter
+            return CodeBlock.of("document.$N()", accessor.getElement().getSimpleName().toString());
         }
-
-        // Copy the field into a local variable to make it easier to refer to it repeatedly.
-        CodeBlock.Builder body = CodeBlock.builder()
-                .addStatement(
-                        "$T $NCopy = $L",
-                        propertyType,
-                        fieldName,
-                        createAppSearchFieldRead(fieldName));
-
-        List<? extends TypeMirror> genericTypes = ((DeclaredType) propertyType).getTypeArguments();
-        TypeMirror componentType = genericTypes.get(0);
-
-        if (!tryCollectionForLoopAssign(body, fieldName, propertyName, componentType)         // 1a
-                && !tryCollectionCallToArray(body, fieldName, propertyName, componentType)    // 1b
-                && !tryCollectionForLoopCallToGenericDocument(
-                body, fieldName, propertyName, componentType)) {                        // 1c
-            // Scenario 1x
-            throw new ProcessingException(
-                    "Unhandled out property type (1x): " + propertyType.toString(), property);
-        }
-
-        method.addCode(body.build());
-        return true;
-    }
-
-    //   1a: CollectionForLoopAssign
-    //       Collection contains boxed Long, Integer, Double, Float, Boolean or byte[].
-    //       We have to pack it into a primitive array of type long[], double[], boolean[],
-    //       or byte[][] by reading each element one-by-one and assigning it. The compiler takes
-    //       care of unboxing.
-    private boolean tryCollectionForLoopAssign(
-            @NonNull CodeBlock.Builder method,
-            @NonNull String fieldName,
-            @NonNull String propertyName,
-            @NonNull TypeMirror propertyType) {
-        Types typeUtil = mEnv.getTypeUtils();
-        CodeBlock.Builder body = CodeBlock.builder()
-                .add("if ($NCopy != null) {\n", fieldName).indent();
-
-        String setPropertyMethod;
-        if (typeUtil.isSameType(propertyType, mHelper.mLongBoxType)
-                || typeUtil.isSameType(propertyType, mHelper.mIntegerBoxType)) {
-            setPropertyMethod = "setPropertyLong";
-            body.addStatement(
-                    "long[] $NConv = new long[$NCopy.size()]", fieldName, fieldName);
-
-        } else if (typeUtil.isSameType(propertyType, mHelper.mDoubleBoxType)
-                || typeUtil.isSameType(propertyType, mHelper.mFloatBoxType)) {
-            setPropertyMethod = "setPropertyDouble";
-            body.addStatement(
-                    "double[] $NConv = new double[$NCopy.size()]", fieldName, fieldName);
-
-        } else if (typeUtil.isSameType(propertyType, mHelper.mBooleanBoxType)) {
-            setPropertyMethod = "setPropertyBoolean";
-            body.addStatement(
-                    "boolean[] $NConv = new boolean[$NCopy.size()]", fieldName, fieldName);
-
-        } else if (typeUtil.isSameType(propertyType, mHelper.mBytePrimitiveArrayType)) {
-            setPropertyMethod = "setPropertyBytes";
-            body.addStatement(
-                    "byte[][] $NConv = new byte[$NCopy.size()][]", fieldName, fieldName);
-
-        } else {
-            // This is not a type 1a collection.
-            return false;
-        }
-
-        // Iterate over each element of the collection, assigning it to the output array.
-        body.addStatement("int i = 0")
-                .add("for ($T item : $NCopy) {\n", propertyType, fieldName).indent()
-                .addStatement("$NConv[i++] = item", fieldName)
-                .unindent().add("}\n")
-                .addStatement("builder.$N($S, $NConv)", setPropertyMethod, propertyName, fieldName)
-                .unindent().add("}\n");
-
-        method.add(body.build());
-        return true;
-    }
-
-    //   1b: CollectionCallToArray
-    //       Collection contains String. We have to convert this into an array of String[] or but no
-    //       conversion of the collection elements is needed. We can use Collection#toArray for
-    //       this.
-    private boolean tryCollectionCallToArray(
-            @NonNull CodeBlock.Builder method,
-            @NonNull String fieldName,
-            @NonNull String propertyName,
-            @NonNull TypeMirror propertyType) {
-        Types typeUtil = mEnv.getTypeUtils();
-        CodeBlock.Builder body = CodeBlock.builder()
-                .add("if ($NCopy != null) {\n", fieldName).indent();
-
-        if (typeUtil.isSameType(propertyType, mHelper.mStringType)) {
-            body.addStatement(
-                    "String[] $NConv = $NCopy.toArray(new String[0])", fieldName, fieldName);
-
-        } else {
-            // This is not a type 1b collection.
-            return false;
-        }
-
-        body.addStatement(
-                "builder.setPropertyString($S, $NConv)", propertyName, fieldName)
-                .unindent().add("}\n");
-
-        method.add(body.build());
-        return true;
-    }
-
-    //   1c: CollectionForLoopCallToGenericDocument
-    //       Collection contains a class which is annotated with @Document.
-    //       We have to convert this into an array of GenericDocument[], by reading each element
-    //       one-by-one and converting it through the standard conversion machinery.
-    private boolean tryCollectionForLoopCallToGenericDocument(
-            @NonNull CodeBlock.Builder method,
-            @NonNull String fieldName,
-            @NonNull String propertyName,
-            @NonNull TypeMirror propertyType) {
-        Types typeUtil = mEnv.getTypeUtils();
-        CodeBlock.Builder body = CodeBlock.builder()
-                .add("if ($NCopy != null) {\n", fieldName).indent();
-
-        Element element = typeUtil.asElement(propertyType);
-        if (element == null) {
-            // The propertyType is not an element, this is not a type 1c list.
-            return false;
-        }
-        if (getDocumentAnnotation(element) == null) {
-            // The propertyType doesn't have @Document annotation, this is not a type 1c
-            // list.
-            return false;
-        }
-
-        body.addStatement(
-                "GenericDocument[] $NConv = new GenericDocument[$NCopy.size()]",
-                fieldName, fieldName);
-        body.addStatement("int i = 0");
-        body
-                .add("for ($T item : $NCopy) {\n", propertyType, fieldName).indent()
-                .addStatement(
-                        "$NConv[i++] = $T.fromDocumentClass(item)",
-                        fieldName, mHelper.getAppSearchClass("GenericDocument"))
-                .unindent().add("}\n");
-
-        body
-                .addStatement("builder.setPropertyDocument($S, $NConv)", propertyName, fieldName)
-                .unindent()
-                .add("}\n");   //  if ($NCopy != null) {
-
-        method.add(body.build());
-        return true;
-    }
-
-    /**
-     * If the given field is an array, generates code to read it and convert it into a form suitable
-     * for GenericDocument and returns true. If the field is not an array, returns false.
-     */
-    private boolean tryConvertFromArray(
-            @NonNull MethodSpec.Builder method,
-            @NonNull String fieldName,
-            @NonNull String propertyName,
-            @NonNull Element property) throws ProcessingException {
-        Types typeUtil = mEnv.getTypeUtils();
-        TypeMirror propertyType = getPropertyType(property);
-        if (propertyType.getKind() != TypeKind.ARRAY
-                // Byte arrays have a native representation in Icing, so they are not considered a
-                // "repeated" type
-                || typeUtil.isSameType(propertyType, mHelper.mBytePrimitiveArrayType)) {
-            return false;  // This is not a scenario 2 array
-        }
-
-        // Copy the field into a local variable to make it easier to refer to it repeatedly.
-        CodeBlock.Builder body = CodeBlock.builder()
-                .addStatement(
-                        "$T $NCopy = $L",
-                        propertyType,
-                        fieldName,
-                        createAppSearchFieldRead(fieldName));
-
-        TypeMirror componentType = ((ArrayType) propertyType).getComponentType();
-
-        if (!tryArrayForLoopAssign(body, fieldName, propertyName, componentType)              // 2a
-                && !tryArrayUseDirectly(body, fieldName, propertyName, componentType)         // 2b
-                && !tryArrayForLoopCallToGenericDocument(
-                body, fieldName, propertyName, componentType)) {                        // 2c
-            // Scenario 2x
-            throw new ProcessingException(
-                    "Unhandled out property type (2x): " + propertyType.toString(), property);
-        }
-
-        method.addCode(body.build());
-        return true;
-    }
-
-    //   2a: ArrayForLoopAssign
-    //       Array is of type Long[], Integer[], int[], Double[], Float[], float[], Boolean[],
-    //       or Byte[].
-    //       We have to pack it into a primitive array of type long[], double[], boolean[] or
-    //       byte[] by reading each element one-by-one and assigning it. The compiler takes care
-    //       of unboxing.
-    private boolean tryArrayForLoopAssign(
-            @NonNull CodeBlock.Builder method,
-            @NonNull String fieldName,
-            @NonNull String propertyName,
-            @NonNull TypeMirror propertyType) {
-        Types typeUtil = mEnv.getTypeUtils();
-        CodeBlock.Builder body = CodeBlock.builder()
-                .add("if ($NCopy != null) {\n", fieldName).indent();
-
-        String setPropertyMethod;
-        if (typeUtil.isSameType(propertyType, mHelper.mLongBoxType)
-                || typeUtil.isSameType(propertyType, mHelper.mIntegerBoxType)
-                || typeUtil.isSameType(propertyType, mHelper.mIntPrimitiveType)) {
-            setPropertyMethod = "setPropertyLong";
-            body.addStatement(
-                    "long[] $NConv = new long[$NCopy.length]", fieldName, fieldName);
-
-        } else if (typeUtil.isSameType(propertyType, mHelper.mDoubleBoxType)
-                || typeUtil.isSameType(propertyType, mHelper.mFloatBoxType)
-                || typeUtil.isSameType(propertyType, mHelper.mFloatPrimitiveType)) {
-            setPropertyMethod = "setPropertyDouble";
-            body.addStatement(
-                    "double[] $NConv = new double[$NCopy.length]", fieldName, fieldName);
-
-        } else if (typeUtil.isSameType(propertyType, mHelper.mBooleanBoxType)) {
-            setPropertyMethod = "setPropertyBoolean";
-            body.addStatement(
-                    "boolean[] $NConv = new boolean[$NCopy.length]", fieldName, fieldName);
-
-        } else if (typeUtil.isSameType(propertyType, mHelper.mByteBoxType)) {
-            setPropertyMethod = "setPropertyBytes";
-            body.addStatement(
-                    "byte[] $NConv = new byte[$NCopy.length]", fieldName, fieldName);
-
-        } else {
-            // This is not a type 2a array.
-            return false;
-        }
-
-        // Iterate over each element of the array, assigning it to the output array.
-        body.add("for (int i = 0 ; i < $NCopy.length ; i++) {\n", fieldName)
-                .indent()
-                .addStatement("$NConv[i] = $NCopy[i]", fieldName, fieldName)
-                .unindent().add("}\n")
-                .addStatement("builder.$N($S, $NConv)", setPropertyMethod, propertyName, fieldName)
-                .unindent().add("}\n");
-
-        method.add(body.build());
-        return true;
-    }
-
-    //   2b: ArrayUseDirectly
-    //       Array is of type String[], long[], double[], boolean[], byte[][].
-    //       We can directly use this field with no conversion.
-    private boolean tryArrayUseDirectly(
-            @NonNull CodeBlock.Builder method,
-            @NonNull String fieldName,
-            @NonNull String propertyName,
-            @NonNull TypeMirror propertyType) {
-        Types typeUtil = mEnv.getTypeUtils();
-        CodeBlock.Builder body = CodeBlock.builder()
-                .add("if ($NCopy != null) {\n", fieldName).indent();
-
-        String setPropertyMethod;
-        if (typeUtil.isSameType(propertyType, mHelper.mStringType)) {
-            setPropertyMethod = "setPropertyString";
-        } else if (typeUtil.isSameType(propertyType, mHelper.mLongPrimitiveType)) {
-            setPropertyMethod = "setPropertyLong";
-        } else if (typeUtil.isSameType(propertyType, mHelper.mDoublePrimitiveType)) {
-            setPropertyMethod = "setPropertyDouble";
-        } else if (typeUtil.isSameType(propertyType, mHelper.mBooleanPrimitiveType)) {
-            setPropertyMethod = "setPropertyBoolean";
-        } else if (typeUtil.isSameType(propertyType, mHelper.mBytePrimitiveArrayType)) {
-            setPropertyMethod = "setPropertyBytes";
-        } else {
-            // This is not a type 2b array.
-            return false;
-        }
-
-        body.addStatement(
-                        "builder.$N($S, $NCopy)", setPropertyMethod, propertyName, fieldName)
-                .unindent().add("}\n");
-
-        method.add(body.build());
-        return true;
-    }
-
-    //   2c: ArrayForLoopCallToGenericDocument
-    //       Array is of a class which is annotated with @Document.
-    //       We have to convert this into an array of GenericDocument[], by reading each element
-    //       one-by-one and converting it through the standard conversion machinery.
-    private boolean tryArrayForLoopCallToGenericDocument(
-            @NonNull CodeBlock.Builder method,
-            @NonNull String fieldName,
-            @NonNull String propertyName,
-            @NonNull TypeMirror propertyType) {
-        Types typeUtil = mEnv.getTypeUtils();
-        CodeBlock.Builder body = CodeBlock.builder()
-                .add("if ($NCopy != null) {\n", fieldName).indent();
-
-        Element element = typeUtil.asElement(propertyType);
-        if (element == null) {
-            // The propertyType is not an element, this is not a type 1c list.
-            return false;
-        }
-        if (getDocumentAnnotation(element) == null)  {
-            // The propertyType doesn't have @Document annotation, this is not a type 1c
-            // list.
-            return false;
-        }
-
-        body.addStatement(
-                "GenericDocument[] $NConv = new GenericDocument[$NCopy.length]",
-                fieldName, fieldName);
-        body
-                .add("for (int i = 0; i < $NConv.length; i++) {\n", fieldName).indent()
-                .addStatement(
-                        "$NConv[i] = $T.fromDocumentClass($NCopy[i])",
-                        fieldName, mHelper.getAppSearchClass("GenericDocument"), fieldName)
-                .unindent().add("}\n");
-
-        body.addStatement("builder.setPropertyDocument($S, $NConv)", propertyName, fieldName)
-                .unindent().add("}\n");    //  if ($NCopy != null) {
-
-        method.add(body.build());
-        return true;
-    }
-
-    /**
-     * Given a field which is a single element (non-collection), generates code to read it and
-     * convert it into a form suitable for GenericDocument.
-     */
-    private void convertFromField(
-            @NonNull MethodSpec.Builder method,
-            @NonNull String fieldName,
-            @NonNull String propertyName,
-            @NonNull Element property) throws ProcessingException {
-        // TODO(b/156296904): Handle scenario 3c (FieldCallToGenericDocument)
-        TypeMirror propertyType = getPropertyType(property);
-        CodeBlock.Builder body = CodeBlock.builder();
-        if (!tryFieldUseDirectlyWithNullCheck(
-                body, fieldName, propertyName, propertyType)  // 3a
-                && !tryFieldUseDirectlyWithoutNullCheck(
-                body, fieldName, propertyName, propertyType)  // 3b
-                && !tryFieldCallToGenericDocument(
-                body, fieldName, propertyName, propertyType)) {  // 3c
-            throw new ProcessingException("Unhandled property type.", property);
-        }
-        method.addCode(body.build());
-    }
-
-    //   3a: FieldUseDirectlyWithNullCheck
-    //       Field is of type String, Long, Integer, Double, Float, Boolean, byte[].
-    //       We can use this field directly, after testing for null. The java compiler will box
-    //       or unbox as needed.
-    private boolean tryFieldUseDirectlyWithNullCheck(
-            @NonNull CodeBlock.Builder method,
-            @NonNull String fieldName,
-            @NonNull String propertyName,
-            @NonNull TypeMirror propertyType) {
-        Types typeUtil = mEnv.getTypeUtils();
-        // Copy the field into a local variable to make it easier to refer to it repeatedly.
-        CodeBlock.Builder body = CodeBlock.builder()
-                .addStatement(
-                        "$T $NCopy = $L",
-                        propertyType,
-                        fieldName,
-                        createAppSearchFieldRead(fieldName))
-                .add("if ($NCopy != null) {\n", fieldName).indent();
-
-        String setPropertyMethod;
-        if (typeUtil.isSameType(propertyType, mHelper.mStringType)) {
-            setPropertyMethod = "setPropertyString";
-        } else if (typeUtil.isSameType(propertyType, mHelper.mLongBoxType)
-                || typeUtil.isSameType(propertyType, mHelper.mIntegerBoxType)) {
-            setPropertyMethod = "setPropertyLong";
-        } else if (typeUtil.isSameType(propertyType, mHelper.mDoubleBoxType)
-                || typeUtil.isSameType(propertyType, mHelper.mFloatBoxType)) {
-            setPropertyMethod = "setPropertyDouble";
-        } else if (typeUtil.isSameType(propertyType, mHelper.mBooleanBoxType)) {
-            setPropertyMethod = "setPropertyBoolean";
-        } else if (typeUtil.isSameType(propertyType, mHelper.mBytePrimitiveArrayType)) {
-            setPropertyMethod = "setPropertyBytes";
-        } else {
-            // This is not a type 3a field
-            return false;
-        }
-
-        body.addStatement(
-                        "builder.$N($S, $NCopy)", setPropertyMethod, propertyName, fieldName)
-                .unindent().add("}\n");
-
-        method.add(body.build());
-        return true;
-    }
-
-    //   3b: FieldUseDirectlyWithoutNullCheck
-    //       Field is of type long, int, double, float, or boolean.
-    //       We can use this field directly without testing for null.
-    private boolean tryFieldUseDirectlyWithoutNullCheck(
-            @NonNull CodeBlock.Builder method,
-            @NonNull String fieldName,
-            @NonNull String propertyName,
-            @NonNull TypeMirror propertyType) {
-        Types typeUtil = mEnv.getTypeUtils();
-        String setPropertyMethod;
-        if (typeUtil.isSameType(propertyType, mHelper.mLongPrimitiveType)
-                || typeUtil.isSameType(propertyType, mHelper.mIntPrimitiveType)) {
-            setPropertyMethod = "setPropertyLong";
-        } else if (typeUtil.isSameType(propertyType, mHelper.mDoublePrimitiveType)
-                || typeUtil.isSameType(propertyType, mHelper.mFloatPrimitiveType)) {
-            setPropertyMethod = "setPropertyDouble";
-        } else if (typeUtil.isSameType(propertyType, mHelper.mBooleanPrimitiveType)) {
-            setPropertyMethod = "setPropertyBoolean";
-        } else {
-            // This is not a type 3b field
-            return false;
-        }
-
-        method.addStatement(
-                "builder.$N($S, $L)",
-                setPropertyMethod,
-                propertyName,
-                createAppSearchFieldRead(fieldName));
-        return true;
-    }
-
-    //   3c: FieldCallToGenericDocument
-    //       Field is of a class which is annotated with @Document.
-    //       We have to convert this into a GenericDocument through the standard conversion
-    //       machinery.
-    private boolean tryFieldCallToGenericDocument(
-            @NonNull CodeBlock.Builder method,
-            @NonNull String fieldName,
-            @NonNull String propertyName,
-            @NonNull TypeMirror propertyType) {
-        Types typeUtil = mEnv.getTypeUtils();
-
-        Element element = typeUtil.asElement(propertyType);
-        if (element == null) {
-            // The propertyType is not an element, this is not a type 3c field.
-            return false;
-        }
-        if (getDocumentAnnotation(element) == null) {
-            // The propertyType doesn't have @Document annotation, this is not a type 3c
-            // field.
-            return false;
-        }
-        method.addStatement(
-                "$T $NCopy = $L", propertyType, fieldName, createAppSearchFieldRead(fieldName));
-
-        method.add("if ($NCopy != null) {\n", fieldName).indent();
-
-        method
-                .addStatement(
-                        "GenericDocument $NConv = $T.fromDocumentClass($NCopy)",
-                        fieldName, mHelper.getAppSearchClass("GenericDocument"), fieldName)
-                .addStatement("builder.setPropertyDocument($S, $NConv)", propertyName, fieldName);
-
-        method.unindent().add("}\n");
-        return true;
-    }
-
-    private void setSpecialFields(MethodSpec.Builder method) {
-        for (DocumentModel.SpecialField specialField :
-                DocumentModel.SpecialField.values()) {
-            String fieldName = mModel.getSpecialFieldName(specialField);
-            if (fieldName == null) {
-                continue;  // The document class doesn't have this field, so no need to set it.
-            }
-            switch (specialField) {
-                case ID:
-                    break;  // Always provided to builder constructor; cannot be set separately.
-                case NAMESPACE:
-                    break;  // Always provided to builder constructor; cannot be set separately.
-                case CREATION_TIMESTAMP_MILLIS:
-                    method.addStatement(
-                            "builder.setCreationTimestampMillis($L)",
-                            createAppSearchFieldReadNumeric(fieldName));
-                    break;
-                case TTL_MILLIS:
-                    method.addStatement(
-                            "builder.setTtlMillis($L)", createAppSearchFieldReadNumeric(fieldName));
-                    break;
-                case SCORE:
-                    method.addStatement(
-                            "builder.setScore($L)", createAppSearchFieldReadNumeric(fieldName));
-                    break;
-            }
-        }
-    }
-
-    private CodeBlock createAppSearchFieldRead(@NonNull String fieldName) {
-        switch (Objects.requireNonNull(mModel.getElementReadKind(fieldName))) {
-            case FIELD:
-                return CodeBlock.of("document.$N", fieldName);
-            case GETTER:
-                String getter = mModel.getGetterForElement(fieldName).getSimpleName().toString();
-                return CodeBlock.of("document.$N()", getter);
-        }
-        return null;
-    }
-
-    private CodeBlock createAppSearchFieldReadNumeric(@NonNull String fieldName) {
-        CodeBlock fieldRead = createAppSearchFieldRead(fieldName);
-
-        TypeMirror fieldType =
-                IntrospectionHelper.getPropertyType(mModel.getAllElements().get(fieldName));
-
-        String toPrimitiveMethod;
-        Object primitiveFallback;
-        if (fieldType.toString().equals("java.lang.Integer")) {
-            toPrimitiveMethod = "intValue";
-            primitiveFallback = 0;
-        } else if (fieldType.toString().equals("java.lang.Long")) {
-            toPrimitiveMethod = "longValue";
-            primitiveFallback = "0L";
-        } else {
-            return fieldRead;
-        }
-
-        return CodeBlock.of("($L != null) ? $L.$L() : $L",
-                fieldRead, fieldRead, toPrimitiveMethod, primitiveFallback);
     }
 }
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/BooleanPropertyAnnotation.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/BooleanPropertyAnnotation.java
index f4fd047..01d7706 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/BooleanPropertyAnnotation.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/BooleanPropertyAnnotation.java
@@ -16,11 +16,13 @@
 
 package androidx.appsearch.compiler.annotationwrapper;
 
+import static androidx.appsearch.compiler.IntrospectionHelper.APPSEARCH_SCHEMA_CLASS;
 import static androidx.appsearch.compiler.IntrospectionHelper.DOCUMENT_ANNOTATION_CLASS;
 
 import androidx.annotation.NonNull;
 
 import com.google.auto.value.AutoValue;
+import com.squareup.javapoet.ClassName;
 
 import java.util.Map;
 
@@ -29,11 +31,14 @@
  */
 @AutoValue
 public abstract class BooleanPropertyAnnotation extends DataPropertyAnnotation {
-    public static final String SIMPLE_CLASS_NAME = "BooleanProperty";
-    public static final String CLASS_NAME = DOCUMENT_ANNOTATION_CLASS + "." + SIMPLE_CLASS_NAME;
+    public static final ClassName CLASS_NAME =
+            DOCUMENT_ANNOTATION_CLASS.nestedClass("BooleanProperty");
+
+    public static final ClassName CONFIG_CLASS =
+            APPSEARCH_SCHEMA_CLASS.nestedClass("BooleanPropertyConfig");
 
     public BooleanPropertyAnnotation() {
-        super(SIMPLE_CLASS_NAME);
+        super(CLASS_NAME, CONFIG_CLASS, /* genericDocSetterName= */"setPropertyBoolean");
     }
 
     /**
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/BytesPropertyAnnotation.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/BytesPropertyAnnotation.java
index b367e2f..53b7a37 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/BytesPropertyAnnotation.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/BytesPropertyAnnotation.java
@@ -16,11 +16,13 @@
 
 package androidx.appsearch.compiler.annotationwrapper;
 
+import static androidx.appsearch.compiler.IntrospectionHelper.APPSEARCH_SCHEMA_CLASS;
 import static androidx.appsearch.compiler.IntrospectionHelper.DOCUMENT_ANNOTATION_CLASS;
 
 import androidx.annotation.NonNull;
 
 import com.google.auto.value.AutoValue;
+import com.squareup.javapoet.ClassName;
 
 import java.util.Map;
 
@@ -29,11 +31,14 @@
  */
 @AutoValue
 public abstract class BytesPropertyAnnotation extends DataPropertyAnnotation {
-    public static final String SIMPLE_CLASS_NAME = "BytesProperty";
-    public static final String CLASS_NAME = DOCUMENT_ANNOTATION_CLASS + "." + SIMPLE_CLASS_NAME;
+    public static final ClassName CLASS_NAME =
+            DOCUMENT_ANNOTATION_CLASS.nestedClass("BytesProperty");
+
+    public static final ClassName CONFIG_CLASS =
+            APPSEARCH_SCHEMA_CLASS.nestedClass("BytesPropertyConfig");
 
     public BytesPropertyAnnotation() {
-        super(SIMPLE_CLASS_NAME);
+        super(CLASS_NAME, CONFIG_CLASS, /* genericDocSetterName= */"setPropertyBytes");
     }
 
     /**
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DataPropertyAnnotation.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DataPropertyAnnotation.java
index 74ee9b9..25f7f28 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DataPropertyAnnotation.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DataPropertyAnnotation.java
@@ -20,6 +20,8 @@
 import androidx.annotation.Nullable;
 import androidx.appsearch.compiler.IntrospectionHelper;
 
+import com.squareup.javapoet.ClassName;
+
 import java.util.Map;
 
 import javax.lang.model.element.AnnotationMirror;
@@ -44,10 +46,21 @@
     }
 
     @NonNull
-    private final String mSimpleClassName;
+    private final ClassName mClassName;
 
-    DataPropertyAnnotation(@NonNull String simpleClassName) {
-        mSimpleClassName = simpleClassName;
+    @NonNull
+    private final ClassName mConfigClassName;
+
+    @NonNull
+    private final String mGenericDocSetterName;
+
+    DataPropertyAnnotation(
+            @NonNull ClassName className,
+            @NonNull ClassName configClassName,
+            @NonNull String genericDocSetterName) {
+        mClassName = className;
+        mConfigClassName = configClassName;
+        mGenericDocSetterName = genericDocSetterName;
     }
 
     /**
@@ -63,22 +76,21 @@
             @NonNull IntrospectionHelper helper) {
         Map<String, Object> annotationParams = helper.getAnnotationParams(annotation);
         String qualifiedClassName = annotation.getAnnotationType().toString();
-        switch (qualifiedClassName) {
-            case BooleanPropertyAnnotation.CLASS_NAME:
-                return BooleanPropertyAnnotation.parse(annotationParams, defaultName);
-            case BytesPropertyAnnotation.CLASS_NAME:
-                return BytesPropertyAnnotation.parse(annotationParams, defaultName);
-            case DocumentPropertyAnnotation.CLASS_NAME:
-                return DocumentPropertyAnnotation.parse(annotationParams, defaultName);
-            case DoublePropertyAnnotation.CLASS_NAME:
-                return DoublePropertyAnnotation.parse(annotationParams, defaultName);
-            case LongPropertyAnnotation.CLASS_NAME:
-                return LongPropertyAnnotation.parse(annotationParams, defaultName);
-            case StringPropertyAnnotation.CLASS_NAME:
-                return StringPropertyAnnotation.parse(annotationParams, defaultName);
-            default:
-                return null;
+        if (qualifiedClassName.equals(BooleanPropertyAnnotation.CLASS_NAME.canonicalName())) {
+            return BooleanPropertyAnnotation.parse(annotationParams, defaultName);
+        } else if (qualifiedClassName.equals(BytesPropertyAnnotation.CLASS_NAME.canonicalName())) {
+            return BytesPropertyAnnotation.parse(annotationParams, defaultName);
+        } else if (qualifiedClassName.equals(
+                DocumentPropertyAnnotation.CLASS_NAME.canonicalName())) {
+            return DocumentPropertyAnnotation.parse(annotationParams, defaultName);
+        } else if (qualifiedClassName.equals(DoublePropertyAnnotation.CLASS_NAME.canonicalName())) {
+            return DoublePropertyAnnotation.parse(annotationParams, defaultName);
+        } else if (qualifiedClassName.equals(LongPropertyAnnotation.CLASS_NAME.canonicalName())) {
+            return LongPropertyAnnotation.parse(annotationParams, defaultName);
+        } else if (qualifiedClassName.equals(StringPropertyAnnotation.CLASS_NAME.canonicalName())) {
+            return StringPropertyAnnotation.parse(annotationParams, defaultName);
         }
+        return null;
     }
 
     /**
@@ -94,8 +106,25 @@
 
     @NonNull
     @Override
-    public final String getSimpleClassName() {
-        return mSimpleClassName;
+    public final ClassName getClassName() {
+        return mClassName;
+    }
+
+    /**
+     * The class used to configure data properties of this kind.
+     *
+     * <p>For example, {@link androidx.appsearch.app.AppSearchSchema.StringPropertyConfig} for
+     * {@link StringPropertyAnnotation}.
+     */
+    @NonNull
+    public final ClassName getConfigClassName() {
+        return mConfigClassName;
+    }
+
+    @NonNull
+    @Override
+    public final String getGenericDocSetterName() {
+        return mGenericDocSetterName;
     }
 
     @NonNull
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DocumentPropertyAnnotation.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DocumentPropertyAnnotation.java
index 732d891..3922775 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DocumentPropertyAnnotation.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DocumentPropertyAnnotation.java
@@ -16,11 +16,13 @@
 
 package androidx.appsearch.compiler.annotationwrapper;
 
+import static androidx.appsearch.compiler.IntrospectionHelper.APPSEARCH_SCHEMA_CLASS;
 import static androidx.appsearch.compiler.IntrospectionHelper.DOCUMENT_ANNOTATION_CLASS;
 
 import androidx.annotation.NonNull;
 
 import com.google.auto.value.AutoValue;
+import com.squareup.javapoet.ClassName;
 
 import java.util.Map;
 
@@ -29,11 +31,14 @@
  */
 @AutoValue
 public abstract class DocumentPropertyAnnotation extends DataPropertyAnnotation {
-    public static final String SIMPLE_CLASS_NAME = "DocumentProperty";
-    public static final String CLASS_NAME = DOCUMENT_ANNOTATION_CLASS + "." + SIMPLE_CLASS_NAME;
+    public static final ClassName CLASS_NAME =
+            DOCUMENT_ANNOTATION_CLASS.nestedClass("DocumentProperty");
+
+    public static final ClassName CONFIG_CLASS =
+            APPSEARCH_SCHEMA_CLASS.nestedClass("DocumentPropertyConfig");
 
     public DocumentPropertyAnnotation() {
-        super(SIMPLE_CLASS_NAME);
+        super(CLASS_NAME, CONFIG_CLASS, /* genericDocSetterName= */"setPropertyDocument");
     }
 
     /**
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DoublePropertyAnnotation.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DoublePropertyAnnotation.java
index 2b14892..6313b7e 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DoublePropertyAnnotation.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/DoublePropertyAnnotation.java
@@ -16,11 +16,13 @@
 
 package androidx.appsearch.compiler.annotationwrapper;
 
+import static androidx.appsearch.compiler.IntrospectionHelper.APPSEARCH_SCHEMA_CLASS;
 import static androidx.appsearch.compiler.IntrospectionHelper.DOCUMENT_ANNOTATION_CLASS;
 
 import androidx.annotation.NonNull;
 
 import com.google.auto.value.AutoValue;
+import com.squareup.javapoet.ClassName;
 
 import java.util.Map;
 
@@ -29,11 +31,14 @@
  */
 @AutoValue
 public abstract class DoublePropertyAnnotation extends DataPropertyAnnotation {
-    public static final String SIMPLE_CLASS_NAME = "DoubleProperty";
-    public static final String CLASS_NAME = DOCUMENT_ANNOTATION_CLASS + "." + SIMPLE_CLASS_NAME;
+    public static final ClassName CLASS_NAME =
+            DOCUMENT_ANNOTATION_CLASS.nestedClass("DoubleProperty");
+
+    public static final ClassName CONFIG_CLASS =
+            APPSEARCH_SCHEMA_CLASS.nestedClass("DoublePropertyConfig");
 
     public DoublePropertyAnnotation() {
-        super(SIMPLE_CLASS_NAME);
+        super(CLASS_NAME, CONFIG_CLASS, /* genericDocSetterName= */"setPropertyDouble");
     }
 
     /**
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/LongPropertyAnnotation.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/LongPropertyAnnotation.java
index c519ee8..2767fe8 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/LongPropertyAnnotation.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/LongPropertyAnnotation.java
@@ -16,11 +16,13 @@
 
 package androidx.appsearch.compiler.annotationwrapper;
 
+import static androidx.appsearch.compiler.IntrospectionHelper.APPSEARCH_SCHEMA_CLASS;
 import static androidx.appsearch.compiler.IntrospectionHelper.DOCUMENT_ANNOTATION_CLASS;
 
 import androidx.annotation.NonNull;
 
 import com.google.auto.value.AutoValue;
+import com.squareup.javapoet.ClassName;
 
 import java.util.Map;
 
@@ -29,11 +31,14 @@
  */
 @AutoValue
 public abstract class LongPropertyAnnotation extends DataPropertyAnnotation {
-    public static final String SIMPLE_CLASS_NAME = "LongProperty";
-    public static final String CLASS_NAME = DOCUMENT_ANNOTATION_CLASS + "." + SIMPLE_CLASS_NAME;
+    public static final ClassName CLASS_NAME =
+            DOCUMENT_ANNOTATION_CLASS.nestedClass("LongProperty");
+
+    public static final ClassName CONFIG_CLASS =
+            APPSEARCH_SCHEMA_CLASS.nestedClass("LongPropertyConfig");
 
     public LongPropertyAnnotation() {
-        super(SIMPLE_CLASS_NAME);
+        super(CLASS_NAME, CONFIG_CLASS, /* genericDocSetterName= */"setPropertyLong");
     }
 
     /**
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/MetadataPropertyAnnotation.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/MetadataPropertyAnnotation.java
index bb9f1ce..144d338 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/MetadataPropertyAnnotation.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/MetadataPropertyAnnotation.java
@@ -16,11 +16,13 @@
 
 package androidx.appsearch.compiler.annotationwrapper;
 
-import static java.util.Objects.requireNonNull;
+import static androidx.appsearch.compiler.IntrospectionHelper.DOCUMENT_ANNOTATION_CLASS;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.squareup.javapoet.ClassName;
+
 import java.util.Arrays;
 
 import javax.lang.model.element.AnnotationMirror;
@@ -29,11 +31,13 @@
  * An annotation for a metadata property e.g. {@code @Document.Id}.
  */
 public enum MetadataPropertyAnnotation implements PropertyAnnotation {
-    ID(/* simpleClassName= */"Id"),
-    NAMESPACE(/* simpleClassName= */"Namespace"),
-    CREATION_TIMESTAMP_MILLIS(/* simpleClassName= */"CreationTimestampMillis"),
-    TTL_MILLIS(/* simpleClassName= */"TtlMillis"),
-    SCORE(/* simpleClassName= */"Score");
+    ID(/* simpleClassName= */"Id", /* genericDocSetterName= */"setId"),
+    NAMESPACE(/* simpleClassName= */"Namespace", /* genericDocSetterName= */"setNamespace"),
+    CREATION_TIMESTAMP_MILLIS(
+            /* simpleClassName= */"CreationTimestampMillis",
+            /* genericDocSetterName= */"setCreationTimestampMillis"),
+    TTL_MILLIS(/* simpleClassName= */"TtlMillis", /* genericDocSetterName= */"setTtlMillis"),
+    SCORE(/* simpleClassName= */"Score", /* genericDocSetterName= */"setScore");
 
     /**
      * Attempts to parse an {@link AnnotationMirror} into a {@link MetadataPropertyAnnotation},
@@ -43,28 +47,42 @@
     public static MetadataPropertyAnnotation tryParse(@NonNull AnnotationMirror annotation) {
         String qualifiedClassName = annotation.getAnnotationType().toString();
         return Arrays.stream(values())
-                .filter(val -> val.getQualifiedClassName().equals(qualifiedClassName))
+                .filter(val -> val.getClassName().canonicalName().equals(qualifiedClassName))
                 .findFirst()
                 .orElse(null);
     }
 
     @NonNull
-    private final String mSimpleClassName;
+    @SuppressWarnings("ImmutableEnumChecker") // ClassName is an immutable third-party type
+    private final ClassName mClassName;
 
-    MetadataPropertyAnnotation(@NonNull String simpleClassName) {
-        mSimpleClassName = requireNonNull(simpleClassName);
+    @NonNull
+    private final String mGenericDocSetterName;
+
+    MetadataPropertyAnnotation(
+            @NonNull String simpleClassName, @NonNull String genericDocSetterName) {
+        mClassName = DOCUMENT_ANNOTATION_CLASS.nestedClass(simpleClassName);
+        mGenericDocSetterName = genericDocSetterName;
     }
 
     @Override
     @NonNull
-    public String getSimpleClassName() {
-        return mSimpleClassName;
+    public ClassName getClassName() {
+        return mClassName;
     }
 
+
+
     @Override
     @NonNull
     public PropertyAnnotation.Kind getPropertyKind() {
         return Kind.METADATA_PROPERTY;
     }
+
+    @NonNull
+    @Override
+    public String getGenericDocSetterName() {
+        return mGenericDocSetterName;
+    }
 }
 
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/PropertyAnnotation.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/PropertyAnnotation.java
index b46847b..22a7cb9 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/PropertyAnnotation.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/PropertyAnnotation.java
@@ -16,10 +16,10 @@
 
 package androidx.appsearch.compiler.annotationwrapper;
 
-import static androidx.appsearch.compiler.IntrospectionHelper.DOCUMENT_ANNOTATION_CLASS;
-
 import androidx.annotation.NonNull;
 
+import com.squareup.javapoet.ClassName;
+
 /**
  * An instance of an AppSearch property annotation.
  *
@@ -35,27 +35,25 @@
     }
 
     /**
-     * The annotation class' simple name.
+     * The annotation class' name.
      *
-     * <p>For example, {@code StringProperty} for a {@link StringPropertyAnnotation}.
-     */
-    @NonNull
-    String getSimpleClassName();
-
-    /**
-     * The annotation class' qualified name
-     *
-     * <p>{@code androidx.appsearch.annotation.Document.StringProperty} for a
+     * <p>For example, {@code androidx.appsearch.annotation.Document.StringProperty} for a
      * {@link StringPropertyAnnotation}.
      */
     @NonNull
-    default String getQualifiedClassName() {
-        return DOCUMENT_ANNOTATION_CLASS + "." + getSimpleClassName();
-    }
+    ClassName getClassName();
 
     /**
      * The {@link Kind} of {@link PropertyAnnotation}.
      */
     @NonNull
     Kind getPropertyKind();
+
+    /**
+     * The corresponding setter within {@link androidx.appsearch.app.GenericDocument.Builder}.
+     *
+     * <p>For example, {@code setPropertyString} for a {@link StringPropertyAnnotation}.
+     */
+    @NonNull
+    String getGenericDocSetterName();
 }
diff --git a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/StringPropertyAnnotation.java b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/StringPropertyAnnotation.java
index 7891344..b016dc5 100644
--- a/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/StringPropertyAnnotation.java
+++ b/appsearch/compiler/src/main/java/androidx/appsearch/compiler/annotationwrapper/StringPropertyAnnotation.java
@@ -16,11 +16,13 @@
 
 package androidx.appsearch.compiler.annotationwrapper;
 
+import static androidx.appsearch.compiler.IntrospectionHelper.APPSEARCH_SCHEMA_CLASS;
 import static androidx.appsearch.compiler.IntrospectionHelper.DOCUMENT_ANNOTATION_CLASS;
 
 import androidx.annotation.NonNull;
 
 import com.google.auto.value.AutoValue;
+import com.squareup.javapoet.ClassName;
 
 import java.util.Map;
 
@@ -29,11 +31,14 @@
  */
 @AutoValue
 public abstract class StringPropertyAnnotation extends DataPropertyAnnotation {
-    public static final String SIMPLE_CLASS_NAME = "StringProperty";
-    public static final String CLASS_NAME = DOCUMENT_ANNOTATION_CLASS + "." + SIMPLE_CLASS_NAME;
+    public static final ClassName CLASS_NAME =
+            DOCUMENT_ANNOTATION_CLASS.nestedClass("StringProperty");
+
+    public static final ClassName CONFIG_CLASS =
+            APPSEARCH_SCHEMA_CLASS.nestedClass("StringPropertyConfig");
 
     public StringPropertyAnnotation() {
-        super(SIMPLE_CLASS_NAME);
+        super(CLASS_NAME, CONFIG_CLASS, /* genericDocSetterName= */"setPropertyString");
     }
 
     /**
diff --git a/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java b/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java
index 503dbcf..74b7a3f 100644
--- a/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java
+++ b/appsearch/compiler/src/test/java/androidx/appsearch/compiler/AppSearchCompilerTest.java
@@ -541,15 +541,6 @@
                         + "    String mString;\n"
                         + "}\n");
 
-        checkResultContains(/*className=*/"Gift.java",
-                /*content=*/"builder.setCreationTimestampMillis((document.mCreationTimestampMillis "
-                        + "!= null) ? document.mCreationTimestampMillis.longValue() : 0L)");
-        checkResultContains(/*className=*/"Gift.java",
-                /*content=*/"builder.setTtlMillis((document.getTtlMillis() != null) ? document"
-                        + ".getTtlMillis().longValue() : 0L)");
-        checkResultContains(/*className=*/"Gift.java",
-                /*content=*/"builder.setScore((document.mScore != null) ? document.mScore.intValue"
-                        + "() : 0)");
         checkEqualsGolden("Gift.java");
     }
 
@@ -564,8 +555,8 @@
                         + "}\n");
 
         assertThat(compilation).hadErrorContaining(
-                "Field cannot be read: it is private and we failed to find a suitable getter "
-                        + "for field \"price\"");
+                "Field 'price' cannot be read: it is private and has no suitable getters "
+                        + "[public] int price() OR [public] int getPrice()");
     }
 
     @Test
@@ -580,8 +571,8 @@
                         + "}\n");
 
         assertThat(compilation).hadErrorContaining(
-                "Field cannot be read: it is private and we failed to find a suitable getter "
-                        + "for field \"price\"");
+                "Field 'price' cannot be read: it is private and has no suitable getters "
+                        + "[public] int price() OR [public] int getPrice()");
         assertThat(compilation).hadWarningContaining("Getter cannot be used: private visibility");
     }
 
@@ -597,8 +588,8 @@
                         + "}\n");
 
         assertThat(compilation).hadErrorContaining(
-                "Field cannot be read: it is private and we failed to find a suitable getter "
-                        + "for field \"price\"");
+                "Field 'price' cannot be read: it is private and has no suitable getters "
+                        + "[public] int price() OR [public] int getPrice()");
         assertThat(compilation).hadWarningContaining(
                 "Getter cannot be used: should take no parameters");
     }
@@ -616,8 +607,25 @@
                         + "}\n");
 
         assertThat(compilation).hadErrorContaining(
-                "Field cannot be read: it is private and we failed to find a suitable getter "
-                        + "for field \"price\"");
+                "Field 'price' cannot be read: it is private and has no suitable getters "
+                        + "[public] int price() OR [public] int getPrice()");
+    }
+
+    @Test
+    public void testCantRead_noSuitableBooleanGetter() {
+        Compilation compilation = compile(
+                "@Document\n"
+                        + "public class Gift {\n"
+                        + "  @Document.Namespace String namespace;\n"
+                        + "  @Document.Id String id;\n"
+                        + "  @Document.BooleanProperty private boolean wrapped;\n"
+                        + "}\n");
+
+        assertThat(compilation).hadErrorContaining(
+                "Field 'wrapped' cannot be read: it is private and has no suitable getters "
+                        + "[public] boolean isWrapped() "
+                        + "OR [public] boolean getWrapped() "
+                        + "OR [public] boolean wrapped()");
     }
 
     @Test
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_getter.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_getter.JAVA
index 8a935c1..ec6639e 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_getter.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testAllSpecialFields_getter.JAVA
@@ -39,9 +39,9 @@
   public GenericDocument toGenericDocument(Gift document) throws AppSearchException {
     GenericDocument.Builder<?> builder =
         new GenericDocument.Builder<>(document.namespace, document.getId(), SCHEMA_NAME);
+    builder.setScore(document.getScore());
     builder.setCreationTimestampMillis(document.getCreationTs());
     builder.setTtlMillis(document.getTtlMs());
-    builder.setScore(document.getScore());
     builder.setPropertyLong("price", document.getPrice());
     return builder.build();
   }
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testClassSpecialValues.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testClassSpecialValues.JAVA
index 292f17b..d8539d6 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testClassSpecialValues.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testClassSpecialValues.JAVA
@@ -5,6 +5,8 @@
 import androidx.appsearch.app.GenericDocument;
 import androidx.appsearch.exceptions.AppSearchException;
 import java.lang.Class;
+import java.lang.Integer;
+import java.lang.Long;
 import java.lang.Override;
 import java.lang.String;
 import java.util.Collections;
@@ -41,9 +43,18 @@
   public GenericDocument toGenericDocument(Gift document) throws AppSearchException {
     GenericDocument.Builder<?> builder =
         new GenericDocument.Builder<>(document.mNamespace, document.mId, SCHEMA_NAME);
-    builder.setCreationTimestampMillis((document.mCreationTimestampMillis != null) ? document.mCreationTimestampMillis.longValue() : 0L);
-    builder.setTtlMillis((document.getTtlMillis() != null) ? document.getTtlMillis().longValue() : 0L);
-    builder.setScore((document.mScore != null) ? document.mScore.intValue() : 0);
+    Long mCreationTimestampMillisCopy = document.mCreationTimestampMillis;
+    if (mCreationTimestampMillisCopy != null) {
+      builder.setCreationTimestampMillis(mCreationTimestampMillisCopy);
+    }
+    Integer mScoreCopy = document.mScore;
+    if (mScoreCopy != null) {
+      builder.setScore(mScoreCopy);
+    }
+    Long mTtlMillisCopy = document.getTtlMillis();
+    if (mTtlMillisCopy != null) {
+      builder.setTtlMillis(mTtlMillisCopy);
+    }
     String mStringCopy = document.mString;
     if (mStringCopy != null) {
       builder.setPropertyString("string", mStringCopy);
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testLongPropertyIndexingType.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testLongPropertyIndexingType.JAVA
index 64e240a..b924069 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testLongPropertyIndexingType.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testLongPropertyIndexingType.JAVA
@@ -98,7 +98,7 @@
     Integer[] arrBoxIntCopy = document.arrBoxInt;
     if (arrBoxIntCopy != null) {
       long[] arrBoxIntConv = new long[arrBoxIntCopy.length];
-      for (int i = 0 ; i < arrBoxIntCopy.length ; i++) {
+      for (int i = 0; i < arrBoxIntCopy.length; i++) {
         arrBoxIntConv[i] = arrBoxIntCopy[i];
       }
       builder.setPropertyLong("arrBoxInt", arrBoxIntConv);
@@ -106,7 +106,7 @@
     int[] arrUnboxIntCopy = document.arrUnboxInt;
     if (arrUnboxIntCopy != null) {
       long[] arrUnboxIntConv = new long[arrUnboxIntCopy.length];
-      for (int i = 0 ; i < arrUnboxIntCopy.length ; i++) {
+      for (int i = 0; i < arrUnboxIntCopy.length; i++) {
         arrUnboxIntConv[i] = arrUnboxIntCopy[i];
       }
       builder.setPropertyLong("arrUnboxInt", arrUnboxIntConv);
@@ -114,7 +114,7 @@
     Long[] arrBoxLongCopy = document.arrBoxLong;
     if (arrBoxLongCopy != null) {
       long[] arrBoxLongConv = new long[arrBoxLongCopy.length];
-      for (int i = 0 ; i < arrBoxLongCopy.length ; i++) {
+      for (int i = 0; i < arrBoxLongCopy.length; i++) {
         arrBoxLongConv[i] = arrBoxLongCopy[i];
       }
       builder.setPropertyLong("arrBoxLong", arrBoxLongConv);
diff --git a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testToGenericDocument_allSupportedTypes.JAVA b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testToGenericDocument_allSupportedTypes.JAVA
index ec1e5ac..ba71c9a 100644
--- a/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testToGenericDocument_allSupportedTypes.JAVA
+++ b/appsearch/compiler/src/test/resources/androidx/appsearch/compiler/goldens/testToGenericDocument_allSupportedTypes.JAVA
@@ -239,7 +239,7 @@
     Long[] arrBoxLongCopy = document.arrBoxLong;
     if (arrBoxLongCopy != null) {
       long[] arrBoxLongConv = new long[arrBoxLongCopy.length];
-      for (int i = 0 ; i < arrBoxLongCopy.length ; i++) {
+      for (int i = 0; i < arrBoxLongCopy.length; i++) {
         arrBoxLongConv[i] = arrBoxLongCopy[i];
       }
       builder.setPropertyLong("arrBoxLong", arrBoxLongConv);
@@ -251,7 +251,7 @@
     Integer[] arrBoxIntegerCopy = document.arrBoxInteger;
     if (arrBoxIntegerCopy != null) {
       long[] arrBoxIntegerConv = new long[arrBoxIntegerCopy.length];
-      for (int i = 0 ; i < arrBoxIntegerCopy.length ; i++) {
+      for (int i = 0; i < arrBoxIntegerCopy.length; i++) {
         arrBoxIntegerConv[i] = arrBoxIntegerCopy[i];
       }
       builder.setPropertyLong("arrBoxInteger", arrBoxIntegerConv);
@@ -259,7 +259,7 @@
     int[] arrUnboxIntCopy = document.arrUnboxInt;
     if (arrUnboxIntCopy != null) {
       long[] arrUnboxIntConv = new long[arrUnboxIntCopy.length];
-      for (int i = 0 ; i < arrUnboxIntCopy.length ; i++) {
+      for (int i = 0; i < arrUnboxIntCopy.length; i++) {
         arrUnboxIntConv[i] = arrUnboxIntCopy[i];
       }
       builder.setPropertyLong("arrUnboxInt", arrUnboxIntConv);
@@ -267,7 +267,7 @@
     Double[] arrBoxDoubleCopy = document.arrBoxDouble;
     if (arrBoxDoubleCopy != null) {
       double[] arrBoxDoubleConv = new double[arrBoxDoubleCopy.length];
-      for (int i = 0 ; i < arrBoxDoubleCopy.length ; i++) {
+      for (int i = 0; i < arrBoxDoubleCopy.length; i++) {
         arrBoxDoubleConv[i] = arrBoxDoubleCopy[i];
       }
       builder.setPropertyDouble("arrBoxDouble", arrBoxDoubleConv);
@@ -279,7 +279,7 @@
     Float[] arrBoxFloatCopy = document.arrBoxFloat;
     if (arrBoxFloatCopy != null) {
       double[] arrBoxFloatConv = new double[arrBoxFloatCopy.length];
-      for (int i = 0 ; i < arrBoxFloatCopy.length ; i++) {
+      for (int i = 0; i < arrBoxFloatCopy.length; i++) {
         arrBoxFloatConv[i] = arrBoxFloatCopy[i];
       }
       builder.setPropertyDouble("arrBoxFloat", arrBoxFloatConv);
@@ -287,7 +287,7 @@
     float[] arrUnboxFloatCopy = document.arrUnboxFloat;
     if (arrUnboxFloatCopy != null) {
       double[] arrUnboxFloatConv = new double[arrUnboxFloatCopy.length];
-      for (int i = 0 ; i < arrUnboxFloatCopy.length ; i++) {
+      for (int i = 0; i < arrUnboxFloatCopy.length; i++) {
         arrUnboxFloatConv[i] = arrUnboxFloatCopy[i];
       }
       builder.setPropertyDouble("arrUnboxFloat", arrUnboxFloatConv);
@@ -295,7 +295,7 @@
     Boolean[] arrBoxBooleanCopy = document.arrBoxBoolean;
     if (arrBoxBooleanCopy != null) {
       boolean[] arrBoxBooleanConv = new boolean[arrBoxBooleanCopy.length];
-      for (int i = 0 ; i < arrBoxBooleanCopy.length ; i++) {
+      for (int i = 0; i < arrBoxBooleanCopy.length; i++) {
         arrBoxBooleanConv[i] = arrBoxBooleanCopy[i];
       }
       builder.setPropertyBoolean("arrBoxBoolean", arrBoxBooleanConv);
diff --git a/appsearch/exportToFramework.py b/appsearch/exportToFramework.py
index 615eda8..2625438 100755
--- a/appsearch/exportToFramework.py
+++ b/appsearch/exportToFramework.py
@@ -189,6 +189,12 @@
         return contents
 
     def _TransformTestCode(self, contents):
+        # Add imports used in tests
+        imports_to_add = []
+        if '@exportToFramework:SdkSuppress' in contents:
+            imports_to_add.append('androidx.test.filters.SdkSuppress')
+            imports_to_add.append('android.os.Build')
+
         contents = (contents
             .replace('androidx.appsearch.testutil.', 'android.app.appsearch.testutil.')
             .replace(
@@ -199,8 +205,18 @@
                     'android.app.appsearch.AppSearchManager')
             .replace('LocalStorage.', 'AppSearchManager.')
         )
+        contents = re.sub(
+            r'/\*@exportToFramework:SdkSuppress\(minSdkVersion = (.*?)\)\*/',
+            r'@SdkSuppress(minSdkVersion = \1)',
+            contents)
         for shim in ['AppSearchSession', 'GlobalSearchSession', 'SearchResults']:
             contents = re.sub(r"([^a-zA-Z])(%s)([^a-zA-Z0-9])" % shim, r'\1\2Shim\3', contents)
+
+        for import_to_add in imports_to_add:
+            contents = re.sub(
+                    r'^(\s*package [^;]+;\s*)$', r'\1\nimport %s;\n' % import_to_add, contents,
+                    flags=re.MULTILINE)
+
         return self._TransformCommonCode(contents)
 
     def _TransformAndCopyFolder(self, source_dir, dest_dir, transform_func=None):
diff --git a/benchmark/benchmark-common/lint-baseline.xml b/benchmark/benchmark-common/lint-baseline.xml
new file mode 100644
index 0000000..c56433f
--- /dev/null
+++ b/benchmark/benchmark-common/lint-baseline.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.3.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 23 (current min is 14): `stopAllPerfettoProcesses`"
+        errorLine1="        PerfettoHelper.stopAllPerfettoProcesses()"
+        errorLine2="                       ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/androidTest/java/androidx/benchmark/PerfettoHelperTest.kt"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 21 (current min is 14): `getPidsForProcess`"
+        errorLine1="        fun getPerfettoPids() = Shell.getPidsForProcess(if (unbundled) &quot;tracebox&quot; else &quot;perfetto&quot;)"
+        errorLine2="                                      ~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/androidTest/java/androidx/benchmark/PerfettoHelperTest.kt"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 23 (current min is 14): `PerfettoCapture`"
+        errorLine1="        val capture = PerfettoCapture(unbundled)"
+        errorLine2="                      ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/androidTest/java/androidx/benchmark/PerfettoHelperTest.kt"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 23 (current min is 14): `start`"
+        errorLine1="        capture.start("
+        errorLine2="                ~~~~~">
+        <location
+            file="src/androidTest/java/androidx/benchmark/PerfettoHelperTest.kt"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 23 (current min is 14): `isRunning`"
+        errorLine1="        assertTrue(capture.isRunning())"
+        errorLine2="                           ~~~~~~~~~">
+        <location
+            file="src/androidTest/java/androidx/benchmark/PerfettoHelperTest.kt"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 23 (current min is 14): `stopAllPerfettoProcesses`"
+        errorLine1="        PerfettoHelper.stopAllPerfettoProcesses()"
+        errorLine2="                       ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/androidTest/java/androidx/benchmark/PerfettoHelperTest.kt"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 23 (current min is 14): `isRunning`"
+        errorLine1="        assertFalse(capture.isRunning())"
+        errorLine2="                            ~~~~~~~~~">
+        <location
+            file="src/androidTest/java/androidx/benchmark/PerfettoHelperTest.kt"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 23 (current min is 14): `isAbiSupported`"
+        errorLine1="        Assume.assumeTrue(PerfettoHelper.isAbiSupported())"
+        errorLine2="                                         ~~~~~~~~~~~~~~">
+        <location
+            file="src/androidTest/java/androidx/benchmark/PerfettoHelperTest.kt"/>
+    </issue>
+
+</issues>
diff --git a/benchmark/benchmark-junit4/build.gradle b/benchmark/benchmark-junit4/build.gradle
index ba2cecb..ed93e83 100644
--- a/benchmark/benchmark-junit4/build.gradle
+++ b/benchmark/benchmark-junit4/build.gradle
@@ -37,7 +37,7 @@
     api(libs.kotlinStdlib)
 
     implementation("androidx.test:rules:1.5.0")
-    implementation("androidx.test:runner:1.5.0")
+    implementation("androidx.test:runner:1.5.2")
     implementation("androidx.tracing:tracing-ktx:1.1.0")
     api("androidx.annotation:annotation:1.1.0")
 
diff --git a/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/InstrumentationResultsRunListener.kt b/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/InstrumentationResultsRunListener.kt
index 32837e2..49a7697 100644
--- a/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/InstrumentationResultsRunListener.kt
+++ b/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/InstrumentationResultsRunListener.kt
@@ -29,7 +29,7 @@
  * See [InstrumentationResults.runEndResultBundle]
  *
  */
-@Suppress("unused") // referenced by inst arg at runtime
+@Suppress("unused", "RestrictedApiAndroidX") // referenced by inst arg at runtime
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 public class InstrumentationResultsRunListener : InstrumentationRunListener() {
     override fun instrumentationRunFinished(
diff --git a/benchmark/benchmark-macro-junit4/build.gradle b/benchmark/benchmark-macro-junit4/build.gradle
index 40cc011..1e1dcaf4 100644
--- a/benchmark/benchmark-macro-junit4/build.gradle
+++ b/benchmark/benchmark-macro-junit4/build.gradle
@@ -36,10 +36,10 @@
     api(libs.kotlinStdlib)
     api("androidx.annotation:annotation:1.1.0")
     api(project(":benchmark:benchmark-macro"))
-    api("androidx.test.uiautomator:uiautomator:2.2.0")
+    api("androidx.test.uiautomator:uiautomator:2.3.0-alpha04")
     implementation(project(":benchmark:benchmark-common"))
     implementation("androidx.test:rules:1.5.0")
-    implementation("androidx.test:runner:1.5.0")
+    implementation("androidx.test:runner:1.5.2")
 
     androidTestImplementation(project(":internal-testutils-ktx"))
     androidTestImplementation(libs.testExtJunit)
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/MacrobenchmarkScopeTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/MacrobenchmarkScopeTest.kt
index 3e42bcb..3db43e4 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/MacrobenchmarkScopeTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/MacrobenchmarkScopeTest.kt
@@ -221,16 +221,17 @@
         // Launch first activity, and validate it is displayed
         scope.startActivityAndWait(ConfigurableActivity.createIntent("InitialText"))
         assertTrue(device.hasObject(By.text("InitialText")))
-        scope.stopMethodTracing()
+        scope.stopMethodTracing("TEST_UNIQUE_NAME")
         val outputs = Outputs.outputDirectory.walk().filter {
             it.isFile
         }.toSet()
         val testOutputs = outputs - files
         val trace = testOutputs.singleOrNull { file ->
-            file.absolutePath.endsWith("method.trace")
+            file.name.endsWith(".trace") && file.name.contains("_method_")
         }
         // One method trace should have been created
         assertNotNull(trace)
+        assertTrue(trace.name.startsWith("TEST_UNIQUE_NAME_method_"))
     }
 
     private fun validateLaunchAndFrameStats(pressHome: Boolean) {
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 1514db6..54c3276 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
@@ -48,6 +48,8 @@
 ) {
     val scope = buildMacrobenchmarkScope(packageName)
     val startTime = System.nanoTime()
+    // Ensure the device is awake
+    scope.device.wakeUp()
     val killProcessBlock = scope.killProcessBlock()
     // always kill the process at beginning of a collection.
     killProcessBlock.invoke()
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
index 217cf5d..f3214ed 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
@@ -233,7 +233,7 @@
     // output, and give it different (test-wide) lifecycle
     val perfettoCollector = PerfettoCaptureWrapper()
     val tracePaths = mutableListOf<String>()
-    val resultFiles = mutableListOf<Profiler.ResultFile>()
+    val methodTracingResultFiles = mutableListOf<Profiler.ResultFile>()
     try {
         metrics.forEach {
             it.configure(packageName)
@@ -291,12 +291,12 @@
                                 it.stop()
                             }
                             if (launchWithMethodTracing && scope.isMethodTracing) {
-                                val (label, tracePath) = scope.stopMethodTracing()
+                                val (label, tracePath) = scope.stopMethodTracing(fileLabel)
                                 val resultFile = Profiler.ResultFile(
                                     label = label,
                                     absolutePath = tracePath
                                 )
-                                resultFiles += resultFile
+                                methodTracingResultFiles += resultFile
                                 scope.isMethodTracing = false
                             }
                         }
@@ -348,12 +348,18 @@
             """.trimIndent()
         }
         InstrumentationResults.instrumentationReport {
+            if (launchWithMethodTracing && methodTracingResultFiles.size < iterations) {
+                warningMessage += "\nNOTE: Method traces cannot be captured during iterations" +
+                    " that start while the target process is already running (including HOT/WARM" +
+                    " launches)."
+            }
+
             reportSummaryToIde(
                 warningMessage = warningMessage,
                 testName = uniqueName,
                 measurements = measurements,
                 iterationTracePaths = tracePaths,
-                profilerResults = resultFiles
+                profilerResults = methodTracingResultFiles
             )
 
             warningMessage = "" // warning only printed once
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 5990a47..b8ed386 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
@@ -25,6 +25,7 @@
 import androidx.annotation.VisibleForTesting
 import androidx.benchmark.DeviceInfo
 import androidx.benchmark.Outputs
+import androidx.benchmark.Outputs.dateToFileName
 import androidx.benchmark.Shell
 import androidx.benchmark.macro.MacrobenchmarkScope.Companion.Api24ContextHelper.createDeviceProtectedStorageContextCompat
 import androidx.benchmark.macro.perfetto.forceTrace
@@ -154,7 +155,7 @@
                 ))
             ) {
             isMethodTracing = true
-            val tracePath = methodTracePath(packageName, iteration ?: 0)
+            val tracePath = methodTraceRecordPath(packageName)
             "--start-profiler \"$tracePath\" --streaming"
         } else {
             ""
@@ -310,14 +311,13 @@
      *
      * @return a [Pair] representing the label, and the absolute path of the method trace.
      */
-    @SuppressLint("BanThreadSleep") // Need to sleep to wait for the traces to be flushed.
-    internal fun stopMethodTracing(): Pair<String, String> {
+    internal fun stopMethodTracing(uniqueLabel: String): Pair<String, String> {
         Shell.executeScriptSilent("am profile stop $packageName")
         // Wait for the profiles to get dumped :(
         // ART Method tracing has a buffer size of 8M, so 1 second should be enough
         // to dump the contents of the buffer.
-        val currentIteration = iteration ?: 0
-        val tracePath = methodTracePath(packageName, currentIteration)
+
+        val tracePath = methodTraceRecordPath(packageName)
         // Using 50 ms as a poll duration for a max of 20 iterations. This is because
         // we don't want to wait for longer than 1s. Also, anecdotally when polling from the
         // shell I found a stable iteration count of 3 to be sufficient.
@@ -327,19 +327,23 @@
             stableIterations = 3,
             pollDurationMs = 50L
         )
-        val fileName = methodTraceName(packageName, currentIteration)
+        // unique label so source is clear, dateToFileName so each run of test is unique on host
+        val outputFileName = "$uniqueLabel-method-${dateToFileName()}.trace"
         val stagingFile = File.createTempFile("methodTrace", null, Outputs.dirUsableByAppAndShell)
         // Staging location before we write it again using Outputs.writeFile(...)
+        // NOTE: staging copy may be unnecessary if we just use a single `cp`
         Shell.executeScriptSilent("cp '$tracePath' '$stagingFile'")
-        // Report(
-        val outputPath = Outputs.writeFile(fileName) {
+
+        // Report file
+        val outputPath = Outputs.writeFile(outputFileName) {
             Log.d(TAG, "Writing method traces to ${it.absolutePath}")
             stagingFile.copyTo(it, overwrite = true)
+
             // Cleanup
             stagingFile.delete()
             Shell.executeScriptSilent("rm \"$tracePath\"")
         }
-        return fileName to outputPath
+        return "MethodTrace iteration ${this.iteration ?: 0}" to outputPath
     }
 
     /**
@@ -416,12 +420,11 @@
             return shaderDirectory.absolutePath.replace(context.packageName, packageName)
         }
 
-        fun methodTracePath(packageName: String, iteration: Int): String {
-            return "/data/local/tmp/${methodTraceName(packageName, iteration)}"
-        }
-
-        fun methodTraceName(packageName: String, iteration: Int): String {
-            return "$packageName-$iteration-method.trace"
+        /**
+         * Path for method trace during record, before fully flushed/stopped, move to outputs
+         */
+        fun methodTraceRecordPath(packageName: String): String {
+            return "/data/local/tmp/$packageName-method.trace"
         }
 
         @RequiresApi(Build.VERSION_CODES.N)
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
deleted file mode 100644
index 790413a..0000000
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MethodTracing.kt
+++ /dev/null
@@ -1,89 +0,0 @@
-package androidx.benchmark.macro
-
-import android.content.Context
-import androidx.annotation.RestrictTo
-import androidx.benchmark.Outputs
-import androidx.benchmark.Shell
-import androidx.benchmark.getFirstMountedMediaDir
-import androidx.benchmark.inMemoryTrace
-import androidx.test.platform.app.InstrumentationRegistry
-import java.io.File
-
-private const val OPTION_SAMPLED = "Sampled"
-private const val RECEIVER_NAME = "androidx.benchmark.internal.MethodTracingReceiver"
-
-// Extras
-private const val ACTION = "ACTION"
-private const val UNIQUE_NAME = "UNIQUE_NAME"
-
-// Actions
-private const val METHOD_TRACE_START = "METHOD_TRACE_START"
-private const val METHOD_TRACE_START_SAMPLED = "METHOD_TRACE_START_SAMPLED"
-private const val METHOD_TRACE_END = "ACTION_METHOD_TRACE_END"
-
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-object MethodTracing {
-    private val context: Context = InstrumentationRegistry.getInstrumentation().context
-
-    /**
-     * Starts method tracing on the [packageName].
-     * <br/>
-     * The only [options] supported are `Full` and `Sampled`.
-     */
-    fun startTracing(packageName: String, uniqueName: String, options: String) {
-        val fileName = fileName(uniqueName)
-        // The only options possible are `Full` and `Sampled`
-        val extras = if (options == (OPTION_SAMPLED)) {
-            "-e $ACTION $METHOD_TRACE_START_SAMPLED -e $UNIQUE_NAME $fileName"
-        } else {
-            "-e $ACTION $METHOD_TRACE_START -e $UNIQUE_NAME $fileName"
-        }
-        broadcast(packageName, extras)
-    }
-
-    /**
-     * Stops method tracing and copies the output trace to the `additionalTestOutputDir`.
-     */
-    fun stopTracing(packageName: String, uniqueName: String) {
-        val fileName = fileName(uniqueName)
-        val extras = "-e $ACTION $METHOD_TRACE_END"
-        broadcast(packageName, extras)
-        // The reason we are doing this is to make trace collection possible.
-        // The target APK stores the method traces in the first media mounted directory.
-        // This is because, that happens to be the only directory accessible to the app and shell.
-        // We then subsequently copy it to the actual `Outputs.dirUsableByAppAndShell` from the
-        // perspective of the test APK.
-        val mediaDirParent = context.getFirstMountedMediaDir()?.parentFile
-        val sourcePath = "$mediaDirParent/$packageName/$fileName"
-        // Staging location before we write it again using Outputs.writeFile(...)
-        // :(
-        val stagingPath = "${Outputs.dirUsableByAppAndShell}/_$fileName"
-        Shell.executeScriptSilent("cp '$sourcePath' '$stagingPath'")
-        // Report
-        Outputs.writeFile(fileName) {
-            val staging = File(stagingPath)
-            // No need to clean up, here because the clean up happens automatically on subsequent
-            // test runs.
-            staging.copyTo(it, overwrite = true)
-        }
-    }
-
-    private fun fileName(uniqueName: String): String {
-        return "${uniqueName}_method.trace"
-    }
-
-    private fun broadcast(targetPackageName: String, extras: String) {
-        inMemoryTrace("methodTracingBroadcast") {
-            val action = "androidx.benchmark.experiments.ACTION_METHOD_TRACE"
-            val result =
-                Shell.amBroadcast("-a $action $extras $targetPackageName/$RECEIVER_NAME") ?: 0
-            require(result > 0) {
-                """
-                    Operation with $extras failed (result code $result).
-                    Make sure you add a dependency on:
-                    `project(":benchmark:benchmark-internal")`
-                """.trimIndent()
-            }
-        }
-    }
-}
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 8c6a2e1..9d96b85 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
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import androidx.bluetooth.AdvertiseParams
-import androidx.bluetooth.AdvertiseResult
 import androidx.bluetooth.BluetoothLe
 import java.util.UUID
 import kotlinx.coroutines.cancel
@@ -41,7 +40,7 @@
         val params = AdvertiseParams()
         launch {
             bluetoothLe.advertise(params) { result ->
-                Assert.assertEquals(AdvertiseResult.ADVERTISE_STARTED, result)
+                Assert.assertEquals(BluetoothLe.ADVERTISE_STARTED, result)
                 cancel()
             }
         }
@@ -62,7 +61,7 @@
 
         launch {
             bluetoothLe.advertise(advertiseParams) { result ->
-                Assert.assertEquals(AdvertiseResult.ADVERTISE_FAILED_DATA_TOO_LARGE, result)
+                Assert.assertEquals(BluetoothLe.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 32729d4..09b12f3 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
@@ -35,7 +35,8 @@
 import java.util.UUID
 import java.util.concurrent.atomic.AtomicInteger
 import junit.framework.TestCase.fail
-import kotlinx.coroutines.CompletableDeferred
+import kotlin.coroutines.cancellation.CancellationException
+import kotlin.test.assertFailsWith
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.take
@@ -43,6 +44,7 @@
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert
+import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
@@ -109,32 +111,49 @@
     @Test
     fun connectGatt() = runTest {
         val device = createDevice("00:11:22:33:44:55")
-        val closed = CompletableDeferred<Unit>()
 
         acceptConnect()
 
         bluetoothLe.connectGatt(device) {
+            assertTrue(clientAdapter.shadowBluetoothGatt.isConnected)
+
             Assert.assertEquals(sampleServices.size, services.size)
             sampleServices.forEachIndexed { index, service ->
                 Assert.assertEquals(service.uuid, services[index].uuid)
             }
-            closed.complete(Unit)
         }
 
-        assertTrue(closed.isCompleted)
+        assertTrue(clientAdapter.shadowBluetoothGatt.isClosed)
+        assertFalse(clientAdapter.shadowBluetoothGatt.isConnected)
+    }
+
+    @Test
+    fun connectGatt_throwException_closeGatt() = runTest {
+        val device = createDevice("00:11:22:33:44:55")
+
+        acceptConnect()
+
+        assertFailsWith<RuntimeException> {
+            bluetoothLe.connectGatt(device) {
+                assertTrue(clientAdapter.shadowBluetoothGatt.isConnected)
+                throw RuntimeException()
+            }
+        }
+
+        assertTrue(clientAdapter.shadowBluetoothGatt.isClosed)
+        assertFalse(clientAdapter.shadowBluetoothGatt.isConnected)
     }
 
     @Test
     fun connectFail() = runTest {
         val device = createDevice("00:11:22:33:44:55")
         rejectConnect()
-        assertTrue(runCatching { bluetoothLe.connectGatt(device) { } }.isFailure)
+        assertFailsWith<CancellationException> { bluetoothLe.connectGatt(device) { } }
     }
 
     @Test
     fun readCharacteristic() = runTest {
         val testValue = 48
-        val closed = CompletableDeferred<Unit>()
         val device = createDevice("00:11:22:33:44:55")
         acceptConnect()
 
@@ -158,9 +177,9 @@
                 readCharacteristic(
                     services[0].getCharacteristic(readCharUuid)!!
                 ).getOrNull()?.toInt())
-            closed.complete(Unit)
         }
-        assertTrue(closed.isCompleted)
+        assertTrue(clientAdapter.shadowBluetoothGatt.isClosed)
+        assertFalse(clientAdapter.shadowBluetoothGatt.isConnected)
     }
 
     @Test
@@ -188,7 +207,6 @@
     fun writeCharacteristic() = runTest {
         val initialValue = 48
         val valueToWrite = 96
-        val closed = CompletableDeferred<Unit>()
         val device = createDevice("00:11:22:33:44:55")
         val currentValue = AtomicInteger(initialValue)
 
@@ -229,9 +247,9 @@
                 valueToWrite.toByteArray())
             Assert.assertEquals(valueToWrite,
                 readCharacteristic(characteristic).getOrNull()?.toInt())
-            closed.complete(Unit)
         }
-        assertTrue(closed.isCompleted)
+        assertTrue(clientAdapter.shadowBluetoothGatt.isClosed)
+        assertFalse(clientAdapter.shadowBluetoothGatt.isConnected)
     }
 
     @Test
@@ -260,7 +278,6 @@
     fun subscribeToCharacteristic() = runTest {
         val initialValue = 48
         val valueToNotify = 96
-        val closed = CompletableDeferred<Unit>()
         val device = createDevice("00:11:22:33:44:55")
         val currentValue = AtomicInteger(initialValue)
 
@@ -304,9 +321,9 @@
                 subscribeToCharacteristic(characteristic).first().toInt())
             Assert.assertEquals(valueToNotify,
                 readCharacteristic(characteristic).getOrNull()?.toInt())
-            closed.complete(Unit)
         }
-        assertTrue(closed.isCompleted)
+        assertTrue(clientAdapter.shadowBluetoothGatt.isClosed)
+        assertFalse(clientAdapter.shadowBluetoothGatt.isConnected)
     }
 
     @Test
@@ -369,33 +386,31 @@
     private fun acceptConnect() {
         clientAdapter.onConnectListener =
             StubClientFrameworkAdapter.OnConnectListener { device, _ ->
-            shadowOf(device).simulateGattConnectionChange(
-                BluetoothGatt.GATT_SUCCESS, BluetoothGatt.STATE_CONNECTED
-            )
-            true
-        }
+                clientAdapter.shadowBluetoothGatt.notifyConnection(device.address)
+                true
+            }
 
         clientAdapter.onRequestMtuListener =
             StubClientFrameworkAdapter.OnRequestMtuListener { mtu ->
-            clientAdapter.callback?.onMtuChanged(clientAdapter.bluetoothGatt, mtu,
-                BluetoothGatt.GATT_SUCCESS)
-        }
+                clientAdapter.callback?.onMtuChanged(clientAdapter.bluetoothGatt, mtu,
+                    BluetoothGatt.GATT_SUCCESS)
+            }
 
         clientAdapter.onDiscoverServicesListener =
             StubClientFrameworkAdapter.OnDiscoverServicesListener {
-            clientAdapter.gattServices = sampleServices
-            clientAdapter.callback?.onServicesDiscovered(clientAdapter.bluetoothGatt,
-                BluetoothGatt.GATT_SUCCESS)
-        }
+                clientAdapter.gattServices = sampleServices
+                clientAdapter.callback?.onServicesDiscovered(clientAdapter.bluetoothGatt,
+                    BluetoothGatt.GATT_SUCCESS)
+            }
     }
 
     private fun rejectConnect() {
         clientAdapter.onConnectListener =
             StubClientFrameworkAdapter.OnConnectListener { device, _ ->
-            shadowOf(device).simulateGattConnectionChange(
-                BluetoothGatt.GATT_FAILURE, BluetoothGatt.STATE_DISCONNECTED
-            )
-            false
+                shadowOf(device).simulateGattConnectionChange(
+                    BluetoothGatt.GATT_FAILURE, BluetoothGatt.STATE_DISCONNECTED
+                )
+                false
         }
     }
 
@@ -478,6 +493,10 @@
                 ?.onSetCharacteristicNotification(characteristic, enable)
         }
 
+        override fun closeGatt() {
+            baseAdapter.closeGatt()
+        }
+
         fun interface OnConnectListener {
             fun onConnect(device: FwkDevice, callback: BluetoothGattCallback): Boolean
         }
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 741377c..14f8951 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
@@ -42,6 +42,7 @@
 import kotlinx.coroutines.runBlocking
 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
@@ -114,8 +115,8 @@
             connectRequests.first().accept {}
         }
 
-        Assert.assertTrue(opened.isCompleted)
-        Assert.assertTrue(closed.isCompleted)
+        assertTrue(opened.isCompleted)
+        assertTrue(closed.isCompleted)
     }
 
     @Test
@@ -149,7 +150,7 @@
             }
         }.join()
 
-        Assert.assertTrue(closed.isCompleted)
+        assertTrue(closed.isCompleted)
         Assert.assertEquals(0, serverAdapter.shadowGattServer.responses.size)
     }
 
@@ -182,7 +183,7 @@
             }
         }.join()
 
-        Assert.assertTrue(closed.isCompleted)
+        assertTrue(closed.isCompleted)
         Assert.assertEquals(0, serverAdapter.shadowGattServer.responses.size)
     }
 
@@ -222,7 +223,7 @@
         }.join()
 
         // Ensure if the server is closed
-        Assert.assertTrue(closed.isCompleted)
+        assertTrue(closed.isCompleted)
         Assert.assertEquals(1, serverAdapter.shadowGattServer.responses.size)
         Assert.assertEquals(valueToRead, serverAdapter.shadowGattServer.responses[0].toInt())
     }
@@ -270,8 +271,8 @@
         }.join()
 
         // Ensure if the server is closed
-        Assert.assertTrue(closed.isCompleted)
-        Assert.assertTrue(responsed.isCompleted)
+        assertTrue(closed.isCompleted)
+        assertTrue(responsed.isCompleted)
     }
 
     @Test
@@ -313,7 +314,7 @@
             }
         }.join()
 
-        Assert.assertTrue(closed.isCompleted)
+        assertTrue(closed.isCompleted)
     }
     @Test
     fun writeCharacteristic() = runTest {
@@ -340,9 +341,9 @@
                 connectRequests.collect {
                     it.accept {
                         when (val request = requests.first()) {
-                            is GattServerRequest.WriteCharacteristic -> {
-                                Assert.assertEquals(valueToWrite, request.value?.toInt())
-                                request.sendResponse(request.value)
+                            is GattServerRequest.WriteCharacteristics -> {
+                                Assert.assertEquals(valueToWrite, request.parts[0].value.toInt())
+                                request.sendResponse()
                             }
 
                             else -> fail("unexpected request")
@@ -354,9 +355,7 @@
             }
         }.join()
 
-        Assert.assertTrue(closed.isCompleted)
-        Assert.assertEquals(1, serverAdapter.shadowGattServer.responses.size)
-        Assert.assertEquals(valueToWrite, serverAdapter.shadowGattServer.responses[0].toInt())
+        assertTrue(closed.isCompleted)
     }
 
     @Test
@@ -364,7 +363,7 @@
         val services = listOf(service1, service2)
         val device = createDevice("00:11:22:33:44:55")
         val closed = CompletableDeferred<Unit>()
-        val responsed = CompletableDeferred<Unit>()
+        val responded = CompletableDeferred<Unit>()
         val valueToWrite = 42
 
         runAfterServicesAreAdded(services.size) {
@@ -385,7 +384,7 @@
                 Assert.assertEquals(1, requestId)
                 Assert.assertNotEquals(GATT_SUCCESS, status)
                 Assert.assertNull(value)
-                responsed.complete(Unit)
+                responded.complete(Unit)
             }
 
         launch {
@@ -393,8 +392,8 @@
                 connectRequests.collect {
                     it.accept {
                         when (val request = requests.first()) {
-                            is GattServerRequest.WriteCharacteristic -> {
-                                Assert.assertEquals(valueToWrite, request.value?.toInt())
+                            is GattServerRequest.WriteCharacteristics -> {
+                                Assert.assertEquals(valueToWrite, request.parts[0].value.toInt())
                                 request.sendFailure()
                             }
 
@@ -407,7 +406,7 @@
             }
         }.join()
 
-        Assert.assertTrue(closed.isCompleted)
+        assertTrue(closed.isCompleted)
     }
 
     @Test
@@ -426,8 +425,9 @@
         }
         serverAdapter.onNotifyCharacteristicChangedListener =
             StubServerFrameworkAdapter.OnNotifyCharacteristicChangedListener {
-                    _, _, _, value ->
+                    fwkDevice, _, _, value ->
                 notified.complete(value.toInt())
+                serverAdapter.callback.onNotificationSent(fwkDevice, GATT_SUCCESS)
             }
         serverAdapter.onCloseGattServerListener =
             StubServerFrameworkAdapter.OnCloseGattServerListener {
@@ -438,7 +438,7 @@
             bluetoothLe.openGattServer(services) {
                 connectRequests.collect {
                     it.accept {
-                        notify(notifyCharacteristic, valueToNotify.toByteArray())
+                        assertTrue(notify(notifyCharacteristic, valueToNotify.toByteArray()))
                         // Close the server
                         this@launch.cancel()
                     }
@@ -447,7 +447,7 @@
         }.join()
 
         // Ensure if the server is closed
-        Assert.assertTrue(closed.isCompleted)
+        assertTrue(closed.isCompleted)
         Assert.assertEquals(valueToNotify, notified.await())
     }
 
@@ -473,7 +473,58 @@
             }
         }.join()
 
-        Assert.assertTrue(opened.isCompleted)
+        assertTrue(opened.isCompleted)
+        assertTrue(closed.isCompleted)
+    }
+
+    @Test
+    fun writeLongCharacteristic() = runTest {
+        val services = listOf(service1, service2)
+        val device = createDevice("00:11:22:33:44:55")
+        val closed = CompletableDeferred<Unit>()
+        val values = listOf(byteArrayOf(0, 1), byteArrayOf(2, 3))
+
+        runAfterServicesAreAdded(services.size) {
+            connectDevice(device) {
+                var offset = 0
+                values.forEachIndexed { index, value ->
+                    serverAdapter.callback.onCharacteristicWriteRequest(
+                        device, /*requestId=*/index + 1, writeCharacteristic.fwkCharacteristic,
+                        /*preparedWrite=*/true, /*responseNeeded=*/false,
+                        offset, value
+                    )
+                    offset += value.size
+                }
+                serverAdapter.callback.onExecuteWrite(device, /*requestId=*/values.size + 1, true)
+            }
+        }
+        serverAdapter.onCloseGattServerListener =
+            StubServerFrameworkAdapter.OnCloseGattServerListener {
+                closed.complete(Unit)
+            }
+
+        launch {
+            bluetoothLe.openGattServer(services) {
+                connectRequests.collect {
+                    it.accept {
+                        when (val request = requests.first()) {
+                            is GattServerRequest.WriteCharacteristics -> {
+                                Assert.assertEquals(values.size, request.parts.size)
+                                values.forEachIndexed { index, value ->
+                                    Assert.assertEquals(value, request.parts[index].value)
+                                }
+                                request.sendResponse()
+                            }
+
+                            else -> fail("unexpected request")
+                        }
+                        // Close the server
+                        this@launch.cancel()
+                    }
+                }
+            }
+        }.join()
+
         Assert.assertTrue(closed.isCompleted)
     }
 
diff --git a/bluetooth/bluetooth/api/current.txt b/bluetooth/bluetooth/api/current.txt
index 65b4cb4..6ad9af9 100644
--- a/bluetooth/bluetooth/api/current.txt
+++ b/bluetooth/bluetooth/api/current.txt
@@ -21,15 +21,6 @@
     property public final boolean shouldIncludeDeviceName;
   }
 
-  public final class 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 INSTANCE;
-  }
-
   public final class BluetoothAddress {
     ctor public BluetoothAddress(String address, int addressType);
     method public String getAddress();
@@ -62,6 +53,15 @@
     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);
+    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.BluetoothLe.Companion Companion;
+  }
+
+  public static final class BluetoothLe.Companion {
   }
 
   public static interface BluetoothLe.GattClientScope {
@@ -91,7 +91,7 @@
   public static interface BluetoothLe.GattServerSessionScope {
     method public androidx.bluetooth.BluetoothDevice getDevice();
     method public kotlinx.coroutines.flow.Flow<androidx.bluetooth.GattServerRequest> getRequests();
-    method public void notify(androidx.bluetooth.GattCharacteristic characteristic, byte[] value);
+    method public suspend Object? notify(androidx.bluetooth.GattCharacteristic characteristic, byte[] value, kotlin.coroutines.Continuation<? super java.lang.Boolean>);
     property public abstract androidx.bluetooth.BluetoothDevice device;
     property public abstract kotlinx.coroutines.flow.Flow<androidx.bluetooth.GattServerRequest> requests;
   }
@@ -126,13 +126,20 @@
     property public final androidx.bluetooth.GattCharacteristic characteristic;
   }
 
-  public static final class GattServerRequest.WriteCharacteristic extends androidx.bluetooth.GattServerRequest {
-    method public androidx.bluetooth.GattCharacteristic getCharacteristic();
-    method public byte[]? getValue();
+  public static final class GattServerRequest.WriteCharacteristics extends androidx.bluetooth.GattServerRequest {
+    method public java.util.List<androidx.bluetooth.GattServerRequest.WriteCharacteristics.Part> getParts();
     method public void sendFailure();
-    method public void sendResponse(byte[]? value);
+    method public void sendResponse();
+    property public final java.util.List<androidx.bluetooth.GattServerRequest.WriteCharacteristics.Part> parts;
+  }
+
+  public static final class GattServerRequest.WriteCharacteristics.Part {
+    method public androidx.bluetooth.GattCharacteristic getCharacteristic();
+    method public int getOffset();
+    method public byte[] getValue();
     property public final androidx.bluetooth.GattCharacteristic characteristic;
-    property public final byte[]? value;
+    property public final int offset;
+    property public final byte[] value;
   }
 
   public final class GattService {
diff --git a/bluetooth/bluetooth/api/restricted_current.txt b/bluetooth/bluetooth/api/restricted_current.txt
index 65b4cb4..6ad9af9 100644
--- a/bluetooth/bluetooth/api/restricted_current.txt
+++ b/bluetooth/bluetooth/api/restricted_current.txt
@@ -21,15 +21,6 @@
     property public final boolean shouldIncludeDeviceName;
   }
 
-  public final class 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 INSTANCE;
-  }
-
   public final class BluetoothAddress {
     ctor public BluetoothAddress(String address, int addressType);
     method public String getAddress();
@@ -62,6 +53,15 @@
     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);
+    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.BluetoothLe.Companion Companion;
+  }
+
+  public static final class BluetoothLe.Companion {
   }
 
   public static interface BluetoothLe.GattClientScope {
@@ -91,7 +91,7 @@
   public static interface BluetoothLe.GattServerSessionScope {
     method public androidx.bluetooth.BluetoothDevice getDevice();
     method public kotlinx.coroutines.flow.Flow<androidx.bluetooth.GattServerRequest> getRequests();
-    method public void notify(androidx.bluetooth.GattCharacteristic characteristic, byte[] value);
+    method public suspend Object? notify(androidx.bluetooth.GattCharacteristic characteristic, byte[] value, kotlin.coroutines.Continuation<? super java.lang.Boolean>);
     property public abstract androidx.bluetooth.BluetoothDevice device;
     property public abstract kotlinx.coroutines.flow.Flow<androidx.bluetooth.GattServerRequest> requests;
   }
@@ -126,13 +126,20 @@
     property public final androidx.bluetooth.GattCharacteristic characteristic;
   }
 
-  public static final class GattServerRequest.WriteCharacteristic extends androidx.bluetooth.GattServerRequest {
-    method public androidx.bluetooth.GattCharacteristic getCharacteristic();
-    method public byte[]? getValue();
+  public static final class GattServerRequest.WriteCharacteristics extends androidx.bluetooth.GattServerRequest {
+    method public java.util.List<androidx.bluetooth.GattServerRequest.WriteCharacteristics.Part> getParts();
     method public void sendFailure();
-    method public void sendResponse(byte[]? value);
+    method public void sendResponse();
+    property public final java.util.List<androidx.bluetooth.GattServerRequest.WriteCharacteristics.Part> parts;
+  }
+
+  public static final class GattServerRequest.WriteCharacteristics.Part {
+    method public androidx.bluetooth.GattCharacteristic getCharacteristic();
+    method public int getOffset();
+    method public byte[] getValue();
     property public final androidx.bluetooth.GattCharacteristic characteristic;
-    property public final byte[]? value;
+    property public final int offset;
+    property public final byte[] value;
   }
 
   public final class GattService {
diff --git a/bluetooth/bluetooth/build.gradle b/bluetooth/bluetooth/build.gradle
index 8e7f695..9d626f8 100644
--- a/bluetooth/bluetooth/build.gradle
+++ b/bluetooth/bluetooth/build.gradle
@@ -26,8 +26,6 @@
     implementation(libs.kotlinStdlib)
     implementation(libs.kotlinCoroutinesCore)
 
-    implementation(project(":annotation:annotation-experimental"))
-
     implementation("androidx.annotation:annotation:1.6.0")
 
     androidTestImplementation(libs.testExtJunit)
diff --git a/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothLeTest.kt b/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothLeTest.kt
index 822fd61..d0116af 100644
--- a/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothLeTest.kt
+++ b/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothLeTest.kt
@@ -71,7 +71,7 @@
         val advertiseParams = AdvertiseParams()
 
         bluetoothLe.advertise(advertiseParams) {
-            assertEquals(AdvertiseResult.ADVERTISE_STARTED, it)
+            assertEquals(BluetoothLe.ADVERTISE_STARTED, it)
         }
     }
 
@@ -85,7 +85,7 @@
         )
 
         bluetoothLe.advertise(advertiseParams) {
-            assertEquals(AdvertiseResult.ADVERTISE_FAILED_DATA_TOO_LARGE, it)
+            assertEquals(BluetoothLe.ADVERTISE_FAILED_DATA_TOO_LARGE, it)
         }
     }
 }
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/AdvertiseResult.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/AdvertiseResult.kt
deleted file mode 100644
index 304bdd7..0000000
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/AdvertiseResult.kt
+++ /dev/null
@@ -1,54 +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.bluetooth
-
-import androidx.annotation.IntDef
-import androidx.annotation.RestrictTo
-import kotlin.annotation.Retention
-
-/**
- * An advertise result indicates the result of a request to start advertising, whether success
- * or failure.
- */
-object AdvertiseResult {
-    @Target(AnnotationTarget.TYPE)
-    @RestrictTo(RestrictTo.Scope.LIBRARY)
-    @Retention(AnnotationRetention.SOURCE)
-    @IntDef(
-        ADVERTISE_STARTED,
-        ADVERTISE_FAILED_DATA_TOO_LARGE,
-        ADVERTISE_FAILED_FEATURE_UNSUPPORTED,
-        ADVERTISE_FAILED_INTERNAL_ERROR,
-        ADVERTISE_FAILED_TOO_MANY_ADVERTISERS
-    )
-    annotation class ResultType
-
-    /** 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 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 too many advertisers. */
-    const val ADVERTISE_FAILED_TOO_MANY_ADVERTISERS: Int = 105
-}
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothLe.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothLe.kt
index c4dcf2c..ecb8606 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothLe.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothLe.kt
@@ -29,6 +29,7 @@
 import android.os.ParcelUuid
 import android.util.Log
 import androidx.annotation.DoNotInline
+import androidx.annotation.IntDef
 import androidx.annotation.RequiresApi
 import androidx.annotation.RequiresPermission
 import androidx.annotation.RestrictTo
@@ -52,10 +53,37 @@
  */
 class BluetoothLe constructor(private val context: Context) {
 
-    private companion object {
+    companion object {
         private const val TAG = "BluetoothLe"
+
+        /** 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 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 too many advertisers. */
+        const val ADVERTISE_FAILED_TOO_MANY_ADVERTISERS: Int = 105
     }
 
+    @Target(AnnotationTarget.TYPE)
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @Retention(AnnotationRetention.SOURCE)
+    @IntDef(
+        ADVERTISE_STARTED,
+        ADVERTISE_FAILED_DATA_TOO_LARGE,
+        ADVERTISE_FAILED_FEATURE_UNSUPPORTED,
+        ADVERTISE_FAILED_INTERNAL_ERROR,
+        ADVERTISE_FAILED_TOO_MANY_ADVERTISERS
+    )
+    annotation class AdvertiseResult
+
     @RequiresApi(34)
     private object BluetoothLeApi34Impl {
         @JvmStatic
@@ -103,7 +131,7 @@
     @RequiresPermission("android.permission.BLUETOOTH_ADVERTISE")
     suspend fun advertise(
         advertiseParams: AdvertiseParams,
-        block: (suspend (@AdvertiseResult.ResultType Int) -> Unit)? = null
+        block: (suspend (@AdvertiseResult Int) -> Unit)? = null
     ) {
         val result = CompletableDeferred<Int>()
 
@@ -113,21 +141,21 @@
 
                 when (errorCode) {
                     ADVERTISE_FAILED_DATA_TOO_LARGE ->
-                        result.complete(AdvertiseResult.ADVERTISE_FAILED_DATA_TOO_LARGE)
+                        result.complete(BluetoothLe.ADVERTISE_FAILED_DATA_TOO_LARGE)
 
                     ADVERTISE_FAILED_FEATURE_UNSUPPORTED ->
-                        result.complete(AdvertiseResult.ADVERTISE_FAILED_FEATURE_UNSUPPORTED)
+                        result.complete(BluetoothLe.ADVERTISE_FAILED_FEATURE_UNSUPPORTED)
 
                     ADVERTISE_FAILED_INTERNAL_ERROR ->
-                        result.complete(AdvertiseResult.ADVERTISE_FAILED_INTERNAL_ERROR)
+                        result.complete(BluetoothLe.ADVERTISE_FAILED_INTERNAL_ERROR)
 
                     ADVERTISE_FAILED_TOO_MANY_ADVERTISERS ->
-                        result.complete(AdvertiseResult.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS)
+                        result.complete(BluetoothLe.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS)
                 }
             }
 
             override fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
-                result.complete(AdvertiseResult.ADVERTISE_STARTED)
+                result.complete(ADVERTISE_STARTED)
             }
         }
 
@@ -167,7 +195,7 @@
         }
         result.await().let {
             block?.invoke(it)
-            if (it == AdvertiseResult.ADVERTISE_STARTED) {
+            if (it == ADVERTISE_STARTED) {
                 if (advertiseParams.durationMillis > 0) {
                     delay(advertiseParams.durationMillis.toLong())
                 } else {
@@ -329,7 +357,7 @@
          * A _hot_ [Flow] of incoming requests from the client.
          *
          * A request is either [GattServerRequest.ReadCharacteristic] or
-         * [GattServerRequest.WriteCharacteristic]
+         * [GattServerRequest.WriteCharacteristics]
          */
         val requests: Flow<GattServerRequest>
 
@@ -338,8 +366,10 @@
          *
          * @param characteristic the updated characteristic
          * @param value the new value of the characteristic
+         *
+         * @return `true` if the notification sent successfully
          */
-        fun notify(characteristic: GattCharacteristic, value: ByteArray)
+        suspend fun notify(characteristic: GattCharacteristic, value: ByteArray): Boolean
     }
 
     /**
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattClient.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattClient.kt
index 01456ac..bbd339a 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattClient.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattClient.kt
@@ -81,6 +81,8 @@
         fun writeDescriptor(descriptor: FwkDescriptor, value: ByteArray)
 
         fun setCharacteristicNotification(characteristic: FwkCharacteristic, enable: Boolean)
+
+        fun closeGatt()
     }
 
     @VisibleForTesting
@@ -373,6 +375,9 @@
                 }
             }
         }
+        coroutineContext.job.invokeOnCompletion {
+            fwkAdapter.closeGatt()
+        }
         gattScope.block()
     }
 
@@ -444,6 +449,12 @@
         ) {
             bluetoothGatt?.setCharacteristicNotification(characteristic, enable)
         }
+
+        @RequiresPermission(BLUETOOTH_CONNECT)
+        override fun closeGatt() {
+            bluetoothGatt?.close()
+            bluetoothGatt?.disconnect()
+        }
     }
 
     private open class FrameworkAdapterApi33 : FrameworkAdapterBase() {
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServer.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServer.kt
index 8e1c43c..95a35a7 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServer.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServer.kt
@@ -20,6 +20,7 @@
 import android.annotation.SuppressLint
 import android.bluetooth.BluetoothDevice as FwkDevice
 import android.bluetooth.BluetoothGatt
+import android.bluetooth.BluetoothGatt.GATT_SUCCESS
 import android.bluetooth.BluetoothGattCharacteristic as FwkCharacteristic
 import android.bluetooth.BluetoothGattServer
 import android.bluetooth.BluetoothGattServerCallback
@@ -33,10 +34,13 @@
 import androidx.annotation.VisibleForTesting
 import java.util.concurrent.atomic.AtomicBoolean
 import java.util.concurrent.atomic.AtomicInteger
+import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
 
 /**
  * Class for handling operations as a GATT server role
@@ -72,7 +76,7 @@
         }
 
         val device: BluetoothDevice
-
+        var pendingWriteParts: MutableList<GattServerRequest.WriteCharacteristics.Part>
         suspend fun acceptConnection(block: suspend BluetoothLe.GattServerSessionScope.() -> Unit)
         fun rejectConnection()
 
@@ -83,6 +87,10 @@
         private const val TAG = "GattServer"
     }
 
+    // Should be accessed only from the callback thread
+    private val sessions: MutableMap<FwkDevice, Session> = mutableMapOf()
+    private val attributeMap = AttributeMap()
+
     @SuppressLint("ObsoleteSdkInt")
     @VisibleForTesting
     @RestrictTo(RestrictTo.Scope.LIBRARY)
@@ -102,6 +110,8 @@
             private val attributeMap = AttributeMap()
             // Should be accessed only from the callback thread
             private val sessions: MutableMap<FwkDevice, Session> = mutableMapOf()
+            private val notifyMutex = Mutex()
+            private var notifyJob: CompletableDeferred<Boolean>? = null
 
             override val connectRequests = callbackFlow {
                     attributeMap.updateWithServices(services)
@@ -149,31 +159,77 @@
                         override fun onCharacteristicWriteRequest(
                             device: FwkDevice,
                             requestId: Int,
-                            characteristic: FwkCharacteristic,
+                            fwkCharacteristic: FwkCharacteristic,
                             preparedWrite: Boolean,
                             responseNeeded: Boolean,
                             offset: Int,
-                            value: ByteArray?
+                            value: ByteArray
                         ) {
-                            // TODO(b/296505524): handle preparedWrite == true
-                            attributeMap.fromFwkCharacteristic(characteristic)?.let {
-                                findActiveSessionWithDevice(device)?.run {
-                                    requestChannel.trySend(
-                                        GattServerRequest.WriteCharacteristic(
-                                            this,
-                                            requestId,
-                                            it,
-                                            value
-                                        )
-                                    )
+                            attributeMap.fromFwkCharacteristic(fwkCharacteristic)?.let { char ->
+                                findActiveSessionWithDevice(device)?.let { session ->
+                                    if (preparedWrite) {
+                                        session.pendingWriteParts.add(
+                                            GattServerRequest.WriteCharacteristics.Part(
+                                                char,
+                                                offset,
+                                                value
+                                            ))
+                                        fwkAdapter.sendResponse(device, requestId,
+                                            BluetoothGatt.GATT_SUCCESS, offset, value)
+                                    } else {
+                                        session.requestChannel.trySend(
+                                            GattServerRequest.WriteCharacteristics(
+                                                session,
+                                                requestId,
+                                                listOf(GattServerRequest.WriteCharacteristics.Part(
+                                                    char,
+                                                    0,
+                                                    value
+                                                ))
+                                            ))
+                                    }
                                 }
                             } ?: run {
-                                fwkAdapter.sendResponse(
-                                    device, requestId, BluetoothGatt.GATT_WRITE_NOT_PERMITTED,
-                                    offset, /*value=*/null
-                                )
+                                fwkAdapter.sendResponse(device, requestId,
+                                    BluetoothGatt.GATT_WRITE_NOT_PERMITTED, offset, /*value=*/null)
                             }
                         }
+
+                        override fun onExecuteWrite(
+                            device: FwkDevice,
+                            requestId: Int,
+                            execute: Boolean
+                        ) {
+                            findActiveSessionWithDevice(device)?.let { session ->
+                                if (execute) {
+                                    session.requestChannel.trySend(
+                                        GattServerRequest.WriteCharacteristics(
+                                            session,
+                                            requestId,
+                                            session.pendingWriteParts
+                                        )
+                                    )
+                                } else {
+                                    fwkAdapter.sendResponse(
+                                        device, requestId,
+                                        BluetoothGatt.GATT_SUCCESS, /*offset=*/0, /*value=*/null
+                                    )
+                                }
+                                session.pendingWriteParts = mutableListOf()
+                            } ?: run {
+                                fwkAdapter.sendResponse(device, requestId,
+                                    BluetoothGatt.GATT_WRITE_NOT_PERMITTED,
+                                    /*offset=*/0, /*value=*/null)
+                            }
+                        }
+
+                        override fun onNotificationSent(
+                            device: android.bluetooth.BluetoothDevice?,
+                            status: Int
+                        ) {
+                            notifyJob?.complete(status == GATT_SUCCESS)
+                            notifyJob = null
+                        }
                     }
                     fwkAdapter.openGattServer(context, callback)
                     services.forEach { fwkAdapter.addService(it.fwkService) }
@@ -208,6 +264,8 @@
 
                 val state: AtomicInteger = AtomicInteger(GattServer.Session.STATE_CONNECTING)
                 val requestChannel = Channel<GattServerRequest>(Channel.UNLIMITED)
+                override var pendingWriteParts =
+                    mutableListOf<GattServerRequest.WriteCharacteristics.Part>()
 
                 override suspend fun acceptConnection(
                     block: suspend BluetoothLe.GattServerSessionScope.() -> Unit
@@ -225,16 +283,22 @@
                             get() = this@Session.device
                         override val requests = requestChannel.receiveAsFlow()
 
-                        override fun notify(
+                        override suspend fun notify(
                             characteristic: GattCharacteristic,
                             value: ByteArray
-                        ) {
-                            fwkAdapter.notifyCharacteristicChanged(
-                                device.fwkDevice,
-                                characteristic.fwkCharacteristic,
-                                false,
-                                value
-                            )
+                        ): Boolean {
+                            notifyMutex.withLock {
+                                CompletableDeferred<Boolean>().also {
+                                    notifyJob = it
+                                    fwkAdapter.notifyCharacteristicChanged(
+                                        device.fwkDevice,
+                                        characteristic.fwkCharacteristic,
+                                        false,
+                                        value
+                                    )
+                                    return it.await()
+                                }
+                            }
                         }
                     }
                     scope.block()
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServerRequest.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServerRequest.kt
index 1b477a1..5419aaa 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServerRequest.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattServerRequest.kt
@@ -74,35 +74,44 @@
     }
 
     /**
-     * Represents a write characteristic request.
+     * Represents a request to write characteristics.
      *
-     * @property characteristic a characteristic to write
-     * @property value a value to write
+     * @property parts a list of write request parts
      */
-    class WriteCharacteristic internal constructor(
+    class WriteCharacteristics internal constructor(
         private val session: GattServer.Session,
         private val requestId: Int,
-        val characteristic: GattCharacteristic,
-        val value: ByteArray?
+        val parts: List<Part>
     ) : GattServerRequest() {
         /**
-         * Sends the result for the write request.
-         *
-         * @param value an optional value that is written
+         * Notifies the success of the write request.
          */
-        fun sendResponse(value: ByteArray?) {
+        fun sendResponse() {
             handleRequest {
-                session.sendResponse(requestId, GATT_SUCCESS, 0, value)
+                session.sendResponse(requestId, GATT_SUCCESS, 0, null)
             }
         }
 
         /**
-         * Notifies the failure for the write request.
+         * Notifies the failure of the write request.
          */
         fun sendFailure() {
             handleRequest {
                 session.sendResponse(requestId, GATT_WRITE_NOT_PERMITTED, 0, null)
             }
         }
+
+        /**
+         * A part of write requests.
+         *
+         * @property characteristic a characteristic to write
+         * @property offset an offset of the first octet to be written
+         * @property value a value to be written
+         */
+        class Part internal constructor(
+            val characteristic: GattCharacteristic,
+            val offset: Int,
+            val value: ByteArray
+        )
     }
 }
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 3e1623b..66cddcb 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
@@ -30,7 +30,6 @@
 import android.widget.EditText
 import androidx.appcompat.app.AlertDialog
 import androidx.appcompat.widget.PopupMenu
-import androidx.bluetooth.AdvertiseResult
 import androidx.bluetooth.BluetoothLe
 import androidx.bluetooth.GattCharacteristic
 import androidx.bluetooth.GattServerRequest
@@ -327,26 +326,26 @@
 
             bluetoothLe.advertise(viewModel.advertiseParams) {
                 when (it) {
-                    AdvertiseResult.ADVERTISE_STARTED -> {
+                    BluetoothLe.ADVERTISE_STARTED -> {
                         toast("ADVERTISE_STARTED").show()
                     }
 
-                    AdvertiseResult.ADVERTISE_FAILED_DATA_TOO_LARGE -> {
+                    BluetoothLe.ADVERTISE_FAILED_DATA_TOO_LARGE -> {
                         isAdvertising = false
                         toast("ADVERTISE_FAILED_DATA_TOO_LARGE").show()
                     }
 
-                    AdvertiseResult.ADVERTISE_FAILED_FEATURE_UNSUPPORTED -> {
+                    BluetoothLe.ADVERTISE_FAILED_FEATURE_UNSUPPORTED -> {
                         isAdvertising = false
                         toast("ADVERTISE_FAILED_FEATURE_UNSUPPORTED").show()
                     }
 
-                    AdvertiseResult.ADVERTISE_FAILED_INTERNAL_ERROR -> {
+                    BluetoothLe.ADVERTISE_FAILED_INTERNAL_ERROR -> {
                         isAdvertising = false
                         toast("ADVERTISE_FAILED_INTERNAL_ERROR").show()
                     }
 
-                    AdvertiseResult.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS -> {
+                    BluetoothLe.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS -> {
                         isAdvertising = false
                         toast("ADVERTISE_FAILED_TOO_MANY_ADVERTISERS").show()
                     }
@@ -465,14 +464,15 @@
                     launch {
                         it.accept {
                             requests.collect {
+                                // TODO(b/269390098): handle request correctly
                                 when (it) {
                                     is GattServerRequest.ReadCharacteristic ->
                                         it.sendResponse(
                                             ByteBuffer.allocate(Int.SIZE_BYTES).putInt(1).array()
                                         )
 
-                                    is GattServerRequest.WriteCharacteristic ->
-                                        it.sendResponse(null)
+                                    is GattServerRequest.WriteCharacteristics ->
+                                        it.sendResponse()
 
                                     else -> throw NotImplementedError("unknown request")
                                 }
diff --git a/buildSrc/repos.gradle b/buildSrc/repos.gradle
index 9b5077d..297a520 100644
--- a/buildSrc/repos.gradle
+++ b/buildSrc/repos.gradle
@@ -56,6 +56,12 @@
             mavenPom()
             artifact()
         }
+        if (metalavaRepoOverride != null) {
+            // When using custom metalava repo, do not resolve metalava artifacts from this repo
+            content {
+                excludeGroup "com.android.tools.metalava"
+            }
+        }
     }
     if (System.getenv("ALLOW_PUBLIC_REPOS") != null || System.getProperty("ALLOW_PUBLIC_REPOS") != null) {
         handler.mavenCentral()
diff --git a/busytown/impl/build-metalava-and-androidx.sh b/busytown/impl/build-metalava-and-androidx.sh
index b609c1a..160f418 100755
--- a/busytown/impl/build-metalava-and-androidx.sh
+++ b/busytown/impl/build-metalava-and-androidx.sh
@@ -32,7 +32,7 @@
 
 function buildMetalava() {
   METALAVA_BUILD_LOG="$OUT_DIR/metalava.log"
-  if $gw -p $METALAVA_DIR createArchive --stacktrace --no-daemon > "$METALAVA_BUILD_LOG" 2>&1; then
+  if $gw -p $METALAVA_DIR publish --stacktrace --no-daemon > "$METALAVA_BUILD_LOG" 2>&1; then
     echo built metalava successfully
   else
     cat "$METALAVA_BUILD_LOG" >&2
diff --git a/camera/camera-camera2/lint-baseline.xml b/camera/camera-camera2/lint-baseline.xml
index 82170fb..eb3bdd8 100644
--- a/camera/camera-camera2/lint-baseline.xml
+++ b/camera/camera-camera2/lint-baseline.xml
@@ -1,5 +1,14 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-beta02" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta02)" variant="all" version="8.1.0-beta02">
+<issues format="6" by="lint 8.3.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
+
+    <issue
+        id="NewApi"
+        message="Field requires API level 30 (current min is 21): `android.hardware.camera2.CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE`"
+        errorLine1="                    CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE).getUpper();"
+        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/androidTest/java/androidx/camera/camera2/internal/ZoomControlDeviceTest.java"/>
+    </issue>
 
     <issue
         id="BanThreadSleep"
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
index 3356a20..1cf40e9 100644
--- 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
@@ -20,6 +20,8 @@
 import android.graphics.Matrix
 import android.graphics.Rect
 import android.graphics.SurfaceTexture
+import android.os.Handler
+import android.os.HandlerThread
 import android.util.Size
 import android.view.Surface
 import androidx.camera.core.CameraEffect.PREVIEW
@@ -67,6 +69,7 @@
 
         // The timeout is set to 200ms to qualify for @SmallTest.
         private const val TIMEOUT_MILLIS = 200L
+        private const val THREAD_NAME = "GL_THREAD"
     }
 
     private val size = Size(640, 480)
@@ -80,9 +83,15 @@
     private lateinit var surfaceOutput2: SurfaceOutput
     private lateinit var processor: SurfaceProcessorImpl
     private lateinit var transformationInfo: TransformationInfo
+    private lateinit var glThread: HandlerThread
+    private lateinit var glHandler: Handler
 
     @Before
     fun setUp() {
+        glThread = HandlerThread(THREAD_NAME)
+        glThread.start()
+        glHandler = Handler(glThread.looper)
+
         transformationInfo = TransformationInfo.of(
             cropRect,
             ROTATION_DEGREES,
@@ -112,6 +121,7 @@
         if (::processor.isInitialized) {
             processor.release()
         }
+        glThread.quitSafely()
     }
 
     @Test
@@ -197,7 +207,7 @@
     @Test
     fun replaceOutputSurface_noFrameFromPreviousCycle() = runBlocking {
         // Arrange: setup processor with buffer depth == 1 and fill it full.
-        processor = SurfaceProcessorImpl(1)
+        processor = SurfaceProcessorImpl(1, glHandler)
         withContext(processor.glExecutor.asCoroutineDispatcher()) {
             processor.onInputSurface(surfaceRequest)
             processor.onOutputSurface(surfaceOutput)
@@ -270,7 +280,7 @@
     @Test
     fun drawFrameAfterReplacingOutput_futureCompletesWithInvalidSurface() = runBlocking {
         // Arrange: setup processor with buffer depth == 1 and fill it full.
-        processor = SurfaceProcessorImpl(1)
+        processor = SurfaceProcessorImpl(1, glHandler)
         withContext(processor.glExecutor.asCoroutineDispatcher()) {
             processor.onInputSurface(surfaceRequest)
             processor.onOutputSurface(surfaceOutput)
@@ -344,7 +354,7 @@
         configureProcessor: (SurfaceProcessorImpl) -> Unit = {},
     ): CountDownLatch {
         // Arrange: Create a processor.
-        processor = SurfaceProcessorImpl(queueDepth)
+        processor = SurfaceProcessorImpl(queueDepth, glHandler)
         configureProcessor(processor)
         withContext(processor.glExecutor.asCoroutineDispatcher()) {
             processor.onInputSurface(surfaceRequest)
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
index 4144e51..e46f4b5 100644
--- 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
@@ -25,7 +25,6 @@
 import android.graphics.PorterDuff;
 import android.graphics.SurfaceTexture;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.util.Size;
 import android.view.Surface;
 
@@ -57,10 +56,7 @@
 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;
 
@@ -90,10 +86,8 @@
 
     private boolean mIsReleased = false;
 
-    public SurfaceProcessorImpl(int queueDepth) {
-        mGlThread = new HandlerThread(GL_THREAD_NAME);
-        mGlThread.start();
-        mGlHandler = new Handler(mGlThread.getLooper());
+    public SurfaceProcessorImpl(int queueDepth, @NonNull Handler glHandler) {
+        mGlHandler = glHandler;
         mGlExecutor = CameraXExecutors.newHandlerExecutor(mGlHandler);
         mQueueDepth = queueDepth;
         runOnGlThread(mGlRenderer::init);
@@ -206,7 +200,6 @@
                     mOutputSurfacePair = null;
                 }
                 mGlRenderer.release();
-                mGlThread.quitSafely();
                 mBuffer = null;
                 mOverlayBitmap = null;
                 mOverlayCanvas = null;
@@ -356,7 +349,7 @@
     }
 
     private boolean isGlThread() {
-        return Thread.currentThread() == mGlThread;
+        return Thread.currentThread() == mGlHandler.getLooper().getThread();
     }
 
     @VisibleForTesting
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/AdvancedExtenderImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/AdvancedExtenderImpl.java
index 05259d3..03c6ba2 100644
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/AdvancedExtenderImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/AdvancedExtenderImpl.java
@@ -132,7 +132,8 @@
      * both JPEG and YUV_420_888 format output.
      *
      * <p>The returned sizes must be smaller than or equal to the provided capture size and have the
-     * same aspect ratio as the given capture size.
+     * same aspect ratio as the given capture size. If no supported resolution exists for the
+     * provided capture size then an empty map is returned.
      *
      * @since 1.4
      */
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/OutputSurfaceImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/OutputSurfaceImpl.java
index e7ea060..40b5446 100644
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/OutputSurfaceImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/OutputSurfaceImpl.java
@@ -29,7 +29,7 @@
  */
 public interface OutputSurfaceImpl {
     /**
-     * Gets the surface.
+     * Gets the surface. It returns null if output surface is not specified.
      */
     @Nullable
     Surface getSurface();
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt
index d73811b..b0b4d0c 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/AdvancedSessionProcessorTest.kt
@@ -72,6 +72,7 @@
 import androidx.camera.extensions.impl.advanced.Camera2OutputConfigImplBuilder
 import androidx.camera.extensions.impl.advanced.Camera2SessionConfigImpl
 import androidx.camera.extensions.impl.advanced.Camera2SessionConfigImplBuilder
+import androidx.camera.extensions.impl.advanced.OutputSurfaceConfigurationImpl
 import androidx.camera.extensions.impl.advanced.OutputSurfaceImpl
 import androidx.camera.extensions.impl.advanced.RequestProcessorImpl
 import androidx.camera.extensions.impl.advanced.SessionProcessorImpl
@@ -180,19 +181,19 @@
             // Directly use output surface
             previewConfigBlock = { outputSurfaceImpl ->
                 Camera2OutputConfigImplBuilder
-                    .newSurfaceConfig(outputSurfaceImpl.surface)
+                    .newSurfaceConfig(outputSurfaceImpl.surface!!)
                     .build()
             },
             // Directly use output surface
             captureConfigBlock = { outputSurfaceImpl ->
                 Camera2OutputConfigImplBuilder
-                    .newSurfaceConfig(outputSurfaceImpl.surface)
+                    .newSurfaceConfig(outputSurfaceImpl.surface!!)
                     .build()
             },
             // Directly use output surface
             analysisConfigBlock = { outputSurfaceImpl ->
                 Camera2OutputConfigImplBuilder
-                    .newSurfaceConfig(outputSurfaceImpl.surface)
+                    .newSurfaceConfig(outputSurfaceImpl.surface!!)
                     .build()
             }
         )
@@ -270,13 +271,13 @@
             // Directly use output surface
             previewConfigBlock = { outputSurfaceImpl ->
                 Camera2OutputConfigImplBuilder
-                    .newSurfaceConfig(outputSurfaceImpl.surface)
+                    .newSurfaceConfig(outputSurfaceImpl.surface!!)
                     .build()
             },
             // Directly use output surface
             captureConfigBlock = { outputSurfaceImpl ->
                 Camera2OutputConfigImplBuilder
-                    .newSurfaceConfig(outputSurfaceImpl.surface)
+                    .newSurfaceConfig(outputSurfaceImpl.surface!!)
                     .build()
             },
             // Directly use output surface with shared ImageReader surface.
@@ -286,7 +287,7 @@
                 ).build()
                 sharedConfigId = sharedConfig.id
 
-                Camera2OutputConfigImplBuilder.newSurfaceConfig(outputSurfaceImpl.surface)
+                Camera2OutputConfigImplBuilder.newSurfaceConfig(outputSurfaceImpl.surface!!)
                     .addSurfaceSharingOutputConfig(sharedConfig)
                     .build()
             },
@@ -334,14 +335,14 @@
             // Directly use output surface
             previewConfigBlock = { outputSurfaceImpl ->
                 Camera2OutputConfigImplBuilder
-                    .newSurfaceConfig(outputSurfaceImpl.surface)
+                    .newSurfaceConfig(outputSurfaceImpl.surface!!)
                     .setPhysicalCameraId(physicalCameraId)
                     .build()
             },
             // Directly use output surface
             captureConfigBlock = {
                 Camera2OutputConfigImplBuilder
-                    .newSurfaceConfig(it.surface)
+                    .newSurfaceConfig(it.surface!!)
                     .build()
             },
             // Has intermediate image reader to process YUV
@@ -476,12 +477,12 @@
 class FakeSessionProcessImpl(
     var previewConfigBlock: (OutputSurfaceImpl) -> Camera2OutputConfigImpl = { outputSurfaceImpl ->
         Camera2OutputConfigImplBuilder
-            .newSurfaceConfig(outputSurfaceImpl.surface)
+            .newSurfaceConfig(outputSurfaceImpl.surface!!)
             .build()
     },
     var captureConfigBlock: (OutputSurfaceImpl) -> Camera2OutputConfigImpl = { outputSurfaceImpl ->
             Camera2OutputConfigImplBuilder
-                .newSurfaceConfig(outputSurfaceImpl.surface)
+                .newSurfaceConfig(outputSurfaceImpl.surface!!)
                 .build()
     },
     var analysisConfigBlock: ((OutputSurfaceImpl) -> Camera2OutputConfigImpl)? = null,
@@ -497,15 +498,15 @@
         CompletableDeferred<MutableMap<CaptureRequest.Key<*>, Any>>()
 
     override fun initSession(
-        cameraId: String?,
-        cameraCharacteristicsMap: MutableMap<String, CameraCharacteristics>?,
-        context: Context?,
-        previewSurfaceConfig: OutputSurfaceImpl?,
-        captureSurfaceConfig: OutputSurfaceImpl?,
+        cameraId: String,
+        cameraCharacteristicsMap: MutableMap<String, CameraCharacteristics>,
+        context: Context,
+        previewSurfaceConfig: OutputSurfaceImpl,
+        captureSurfaceConfig: OutputSurfaceImpl,
         analysisSurfaceConfig: OutputSurfaceImpl?
     ): Camera2SessionConfigImpl {
-        captureOutputConfig = captureConfigBlock.invoke(captureSurfaceConfig!!)
-        previewOutputConfig = previewConfigBlock.invoke(previewSurfaceConfig!!)
+        captureOutputConfig = captureConfigBlock.invoke(captureSurfaceConfig)
+        previewOutputConfig = previewConfigBlock.invoke(previewSurfaceConfig)
         analysisSurfaceConfig?.let {
             analysisOutputConfig = analysisConfigBlock?.invoke(it)
         }
@@ -519,22 +520,36 @@
         val sessionBuilder = Camera2SessionConfigImplBuilder().apply {
             addOutputConfig(previewOutputConfig)
             addOutputConfig(captureOutputConfig)
-            analysisOutputConfig?.let { addOutputConfig(analysisOutputConfig) }
+            analysisOutputConfig?.let { addOutputConfig(it) }
         }
         return sessionBuilder.build()
     }
 
+    override fun initSession(
+        cameraId: String,
+        cameraCharacteristicsMap: MutableMap<String, CameraCharacteristics>,
+        context: Context,
+        surfaceConfigs: OutputSurfaceConfigurationImpl
+    ): Camera2SessionConfigImpl {
+        return initSession(
+            cameraId, cameraCharacteristicsMap, context,
+            surfaceConfigs.previewOutputSurface,
+            surfaceConfigs.imageCaptureOutputSurface,
+            surfaceConfigs.imageAnalysisOutputSurface
+        )
+    }
+
     override fun deInitSession() {
     }
 
-    override fun setParameters(parameters: MutableMap<CaptureRequest.Key<*>, Any>?) {
+    override fun setParameters(parameters: MutableMap<CaptureRequest.Key<*>, Any>) {
     }
 
     override fun startTrigger(
-        triggers: MutableMap<CaptureRequest.Key<*>, Any>?,
-        callback: SessionProcessorImpl.CaptureCallback?
+        triggers: MutableMap<CaptureRequest.Key<*>, Any>,
+        callback: SessionProcessorImpl.CaptureCallback
     ): Int {
-        startTriggerParametersDeferred.complete(triggers!!)
+        startTriggerParametersDeferred.complete(triggers)
         return 0
     }
 
@@ -545,15 +560,15 @@
             .isEqualTo(parameters)
     }
 
-    override fun onCaptureSessionStart(requestProcessor: RequestProcessorImpl?) {
+    override fun onCaptureSessionStart(requestProcessor: RequestProcessorImpl) {
         this.requestProcessor = requestProcessor
-        onCaptureSessionStarted?.invoke(requestProcessor!!)
+        onCaptureSessionStarted?.invoke(requestProcessor)
     }
 
     override fun onCaptureSessionEnd() {
     }
 
-    override fun startRepeating(callback: SessionProcessorImpl.CaptureCallback?): Int {
+    override fun startRepeating(callback: SessionProcessorImpl.CaptureCallback): Int {
         val idList = ArrayList<Int>()
         idList.add(previewOutputConfig.id)
         analysisOutputConfig?.let { idList.add(it.id) }
@@ -565,42 +580,42 @@
         val request = RequestProcessorRequest(idList, mapOf(), CameraDevice.TEMPLATE_PREVIEW)
         requestProcessor!!.setRepeating(request, object : RequestProcessorImpl.Callback {
             override fun onCaptureStarted(
-                request: RequestProcessorImpl.Request?,
+                request: RequestProcessorImpl.Request,
                 frameNumber: Long,
                 timestamp: Long
             ) {
-                callback?.onCaptureStarted(currentSequenceId, timestamp)
+                callback.onCaptureStarted(currentSequenceId, timestamp)
             }
 
             override fun onCaptureProgressed(
-                request: RequestProcessorImpl.Request?,
-                partialResult: CaptureResult?
+                request: RequestProcessorImpl.Request,
+                partialResult: CaptureResult
             ) {
             }
 
             @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
             override fun onCaptureCompleted(
-                request: RequestProcessorImpl.Request?,
-                totalCaptureResult: TotalCaptureResult?
+                request: RequestProcessorImpl.Request,
+                totalCaptureResult: TotalCaptureResult
             ) {
-                callback?.onCaptureProcessStarted(currentSequenceId)
-                callback?.onCaptureCompleted(
-                    totalCaptureResult!!.get(CaptureResult.SENSOR_TIMESTAMP)!!,
+                callback.onCaptureProcessStarted(currentSequenceId)
+                callback.onCaptureCompleted(
+                    totalCaptureResult.get(CaptureResult.SENSOR_TIMESTAMP)!!,
                     currentSequenceId, mapOf()
                 )
-                callback?.onCaptureSequenceCompleted(currentSequenceId)
+                callback.onCaptureSequenceCompleted(currentSequenceId)
             }
 
             override fun onCaptureFailed(
-                request: RequestProcessorImpl.Request?,
-                captureFailure: CaptureFailure?
+                request: RequestProcessorImpl.Request,
+                captureFailure: CaptureFailure
             ) {
-                callback?.onCaptureFailed(currentSequenceId)
-                callback?.onCaptureSequenceAborted(currentSequenceId)
+                callback.onCaptureFailed(currentSequenceId)
+                callback.onCaptureSequenceAborted(currentSequenceId)
             }
 
             override fun onCaptureBufferLost(
-                request: RequestProcessorImpl.Request?,
+                request: RequestProcessorImpl.Request,
                 frameNumber: Long,
                 outputStreamId: Int
             ) {
@@ -619,7 +634,7 @@
         requestProcessor?.stopRepeating()
     }
 
-    override fun startCapture(callback: SessionProcessorImpl.CaptureCallback?): Int {
+    override fun startCapture(callback: SessionProcessorImpl.CaptureCallback): Int {
         val idList = ArrayList<Int>()
         idList.add(captureOutputConfig.id)
 
@@ -627,55 +642,63 @@
         val request = RequestProcessorRequest(idList, mapOf(), CameraDevice.TEMPLATE_STILL_CAPTURE)
         requestProcessor?.submit(request, object : RequestProcessorImpl.Callback {
             override fun onCaptureStarted(
-                request: RequestProcessorImpl.Request?,
+                request: RequestProcessorImpl.Request,
                 frameNumber: Long,
                 timestamp: Long
             ) {
-                callback?.onCaptureStarted(currentSequenceId, timestamp)
+                callback.onCaptureStarted(currentSequenceId, timestamp)
             }
 
             override fun onCaptureProgressed(
-                request: RequestProcessorImpl.Request?,
-                partialResult: CaptureResult?
+                request: RequestProcessorImpl.Request,
+                partialResult: CaptureResult
             ) {
             }
 
             @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
             override fun onCaptureCompleted(
-                request: RequestProcessorImpl.Request?,
-                totalCaptureResult: TotalCaptureResult?
+                request: RequestProcessorImpl.Request,
+                totalCaptureResult: TotalCaptureResult
             ) {
-                callback?.onCaptureCompleted(
-                    totalCaptureResult!!.get(CaptureResult.SENSOR_TIMESTAMP)!!, currentSequenceId,
+                callback.onCaptureCompleted(
+                    totalCaptureResult.get(CaptureResult.SENSOR_TIMESTAMP)!!, currentSequenceId,
                     mapOf()
                 )
             }
 
             override fun onCaptureFailed(
-                request: RequestProcessorImpl.Request?,
-                captureFailure: CaptureFailure?
+                request: RequestProcessorImpl.Request,
+                captureFailure: CaptureFailure
             ) {
-                callback?.onCaptureFailed(currentSequenceId)
+                callback.onCaptureFailed(currentSequenceId)
             }
 
             override fun onCaptureBufferLost(
-                request: RequestProcessorImpl.Request?,
+                request: RequestProcessorImpl.Request,
                 frameNumber: Long,
                 outputStreamId: Int
             ) {
             }
 
             override fun onCaptureSequenceCompleted(sequenceId: Int, frameNumber: Long) {
-                callback?.onCaptureSequenceCompleted(currentSequenceId)
+                callback.onCaptureSequenceCompleted(currentSequenceId)
             }
 
             override fun onCaptureSequenceAborted(sequenceId: Int) {
-                callback?.onCaptureSequenceAborted(currentSequenceId)
+                callback.onCaptureSequenceAborted(currentSequenceId)
             }
         })
         return currentSequenceId
     }
 
+    override fun startCaptureWithPostview(callback: SessionProcessorImpl.CaptureCallback): Int {
+        return startCapture(callback)
+    }
+
+    override fun getRealtimeCaptureLatency(): Pair<Long, Long>? {
+        return null
+    }
+
     override fun abortCapture(captureSequenceId: Int) {
         requestProcessor?.abortCaptures()
     }
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt
index c4c0e32..ddd974c 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessorTest.kt
@@ -23,6 +23,7 @@
 import android.hardware.camera2.CaptureRequest
 import android.hardware.camera2.CaptureResult
 import android.hardware.camera2.TotalCaptureResult
+import android.hardware.camera2.params.SessionConfiguration
 import android.media.Image
 import android.media.ImageWriter
 import android.os.Build
@@ -788,6 +789,10 @@
 
         var onEnableSessionCaptureStage: CaptureStageImpl? = null
         var onDisableSessionCaptureStage: CaptureStageImpl? = null
+
+        override fun onSessionType(): Int {
+            return SessionConfiguration.SESSION_REGULAR
+        }
     }
 
     private class FakePreviewExtenderImpl(
@@ -902,13 +907,34 @@
             fakeCaptureProcessorImpl?.close()
             recordInvoking("onDeInit")
         }
+
+        override fun onSessionType(): Int {
+            return SessionConfiguration.SESSION_REGULAR
+        }
+
+        override fun getSupportedPostviewResolutions(captureSize: Size):
+            MutableList<Pair<Int, Array<Size>>>? {
+            return null
+        }
+
+        override fun isCaptureProcessProgressAvailable(): Boolean {
+            return false
+        }
+
+        override fun getRealtimeCaptureLatency(): Pair<Long, Long>? {
+            return null;
+        }
+
+        override fun isPostviewAvailable(): Boolean {
+            return false
+        }
     }
 
     private class FakeCaptureProcessorImpl(
         val throwErrorOnProcess: Boolean = false
     ) : CaptureProcessorImpl {
         private var imageWriter: ImageWriter? = null
-        override fun process(results: MutableMap<Int, Pair<Image, TotalCaptureResult>>?) {
+        override fun process(results: MutableMap<Int, Pair<Image, TotalCaptureResult>>) {
             if (throwErrorOnProcess) {
                 throw RuntimeException("Process failed")
             }
@@ -917,8 +943,8 @@
         }
 
         override fun process(
-            results: MutableMap<Int, Pair<Image, TotalCaptureResult>>?,
-            resultCallback: ProcessResultImpl?,
+            results: MutableMap<Int, Pair<Image, TotalCaptureResult>>,
+            resultCallback: ProcessResultImpl,
             executor: Executor?
         ) {
             process(results)
@@ -930,6 +956,19 @@
 
         override fun onResolutionUpdate(size: Size) {}
         override fun onImageFormatUpdate(imageFormat: Int) {}
+
+        override fun onPostviewOutputSurface(surface: Surface) {}
+
+        override fun onResolutionUpdate(size: Size, postviewSize: Size) {}
+
+        override fun processWithPostview(
+            results: MutableMap<Int, Pair<Image, TotalCaptureResult>>,
+            resultCallback: ProcessResultImpl,
+            executor: Executor?
+        ) {
+            process(results, resultCallback, executor)
+        }
+
         fun close() {
             imageWriter?.close()
             imageWriter = null
@@ -938,15 +977,15 @@
 
     private class FakePreviewImageProcessorImpl : PreviewImageProcessorImpl {
         private var imageWriter: ImageWriter? = null
-        override fun process(image: Image?, result: TotalCaptureResult?) {
+        override fun process(image: Image, result: TotalCaptureResult) {
             val emptyImage = imageWriter!!.dequeueInputImage()
             imageWriter!!.queueInputImage(emptyImage)
         }
 
         override fun process(
-            image: Image?,
-            result: TotalCaptureResult?,
-            resultCallback: ProcessResultImpl?,
+            image: Image,
+            result: TotalCaptureResult,
+            resultCallback: ProcessResultImpl,
             executor: Executor?
         ) {
             process(image, result)
@@ -958,6 +997,7 @@
 
         override fun onResolutionUpdate(size: Size) {}
         override fun onImageFormatUpdate(imageFormat: Int) {}
+
         fun close() {
             imageWriter?.close()
             imageWriter = null
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/PreviewProcessorTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/PreviewProcessorTest.kt
index 0a1d529..b1d29eb 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/PreviewProcessorTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/PreviewProcessorTest.kt
@@ -227,15 +227,15 @@
 
     private class FakePreviewImageProcessorImpl : PreviewImageProcessorImpl {
         private var imageWriter: ImageWriter? = null
-        override fun process(image: Image?, result: TotalCaptureResult?) {
+        override fun process(image: Image, result: TotalCaptureResult) {
             val emptyImage = imageWriter!!.dequeueInputImage()
             imageWriter!!.queueInputImage(emptyImage)
         }
 
         override fun process(
-            image: Image?,
-            result: TotalCaptureResult?,
-            resultCallback: ProcessResultImpl?,
+            image: Image,
+            result: TotalCaptureResult,
+            resultCallback: ProcessResultImpl,
             executor: Executor?
         ) {
             val blankImage = imageWriter!!.dequeueInputImage()
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/StillCaptureProcessorTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/StillCaptureProcessorTest.kt
index 6e33c32..edadb08 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/StillCaptureProcessorTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/StillCaptureProcessorTest.kt
@@ -416,7 +416,7 @@
             throwExceptionDuringProcess = true
         }
         override fun process(
-            results: MutableMap<Int, android.util.Pair<Image, TotalCaptureResult>>?
+            results: MutableMap<Int, android.util.Pair<Image, TotalCaptureResult>>
         ) {
             if (throwExceptionDuringProcess) {
                 throw RuntimeException("Process failed")
@@ -426,8 +426,8 @@
         }
 
         override fun process(
-            results: MutableMap<Int, android.util.Pair<Image, TotalCaptureResult>>?,
-            resultCallback: ProcessResultImpl?,
+            results: MutableMap<Int, android.util.Pair<Image, TotalCaptureResult>>,
+            resultCallback: ProcessResultImpl,
             executor: Executor?
         ) {
             process(results)
@@ -443,6 +443,20 @@
         override fun onImageFormatUpdate(imageFormat: Int) {
         }
 
+        override fun onPostviewOutputSurface(surface: Surface) {
+        }
+
+        override fun onResolutionUpdate(size: Size, postviewSize: Size) {
+        }
+
+        override fun processWithPostview(
+            results: MutableMap<Int, android.util.Pair<Image, TotalCaptureResult>>,
+            resultCallback: ProcessResultImpl,
+            executor: Executor?
+        ) {
+            process(results, resultCallback, executor)
+        }
+
         fun close() {
             imageWriter?.close()
         }
diff --git a/camera/camera-testlib-extensions/build.gradle b/camera/camera-testlib-extensions/build.gradle
index 849e115..7e445ca 100644
--- a/camera/camera-testlib-extensions/build.gradle
+++ b/camera/camera-testlib-extensions/build.gradle
@@ -24,6 +24,7 @@
 
 dependencies {
     api("androidx.annotation:annotation:1.1.0")
+    implementation(project(":camera:camera-core"))
 }
 
 android {
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
index 96e9f73..930f5a2 100755
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
@@ -15,12 +15,12 @@
  */
 package androidx.camera.extensions.impl;
 
-import android.annotation.SuppressLint;
 import android.content.Context;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.media.Image;
 import android.media.ImageWriter;
 import android.os.Build;
@@ -49,7 +49,6 @@
  * @since 1.0
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-@SuppressLint("UnknownNullness")
 public final class AutoImageCaptureExtenderImpl implements ImageCaptureExtenderImpl {
     private static final String TAG = "AutoICExtender";
     private static final int DEFAULT_STAGE_ID = 0;
@@ -79,6 +78,7 @@
         return CameraCharacteristicAvailability.isEffectAvailable(cameraCharacteristics, EFFECT);
     }
 
+    @NonNull
     @Override
     public List<CaptureStageImpl> getCaptureStages() {
         // Placeholder set of CaptureRequest.Key values
@@ -89,6 +89,7 @@
         return captureStages;
     }
 
+    @Nullable
     @Override
     public CaptureProcessorImpl getCaptureProcessor() {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@@ -110,6 +111,7 @@
 
     }
 
+    @Nullable
     @Override
     public CaptureStageImpl onPresetSession() {
         // The CaptureRequest parameters will be set via SessionConfiguration#setSessionParameters
@@ -126,6 +128,7 @@
         return captureStage;
     }
 
+    @Nullable
     @Override
     public CaptureStageImpl onEnableSession() {
         // Set the necessary CaptureRequest parameters via CaptureStage, here we use some
@@ -136,6 +139,7 @@
         return captureStage;
     }
 
+    @Nullable
     @Override
     public CaptureStageImpl onDisableSession() {
         // Set the necessary CaptureRequest parameters via CaptureStage, here we use some
@@ -151,6 +155,7 @@
         return 3;
     }
 
+    @Nullable
     @Override
     public List<Pair<Integer, Size[]>> getSupportedResolutions() {
         return null;
@@ -163,6 +168,33 @@
         return new Range<>(300L, 1000L);
     }
 
+    @Override
+    public int onSessionType() {
+        return SessionConfiguration.SESSION_REGULAR;
+    }
+
+    @Nullable
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(@NonNull Size captureSize) {
+        return null;
+    }
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        return false;
+    }
+
+    @Nullable
+    @Override
+    public Pair<Long, Long> getRealtimeCaptureLatency() {
+        return null;
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        return false;
+    }
+
     @RequiresApi(23)
     static final class AutoImageCaptureExtenderCaptureProcessorImpl implements
             CaptureProcessorImpl {
@@ -174,7 +206,7 @@
         }
 
         @Override
-        public void process(Map<Integer, Pair<Image, TotalCaptureResult>> results) {
+        public void process(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results) {
             Log.d(TAG, "Started auto CaptureProcessor");
 
             Pair<Image, TotalCaptureResult> result = results.get(DEFAULT_STAGE_ID);
@@ -213,9 +245,26 @@
         }
 
         @Override
-        public void process(Map<Integer, Pair<Image, TotalCaptureResult>> results,
-                ProcessResultImpl resultCallback, Executor executor) {
+        public void process(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
+                @NonNull ProcessResultImpl resultCallback, @Nullable Executor executor) {
+            process(results);
+        }
 
+        @Override
+        public void onPostviewOutputSurface(@NonNull Surface surface) {
+
+        }
+
+        @Override
+        public void onResolutionUpdate(@NonNull Size size, @NonNull Size postviewSize) {
+
+        }
+
+        @Override
+        public void processWithPostview(
+                @NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
+                @NonNull ProcessResultImpl resultCallback, @Nullable Executor executor) {
+            throw new UnsupportedOperationException("Postview is not supported");
         }
     }
 
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/AutoPreviewExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/AutoPreviewExtenderImpl.java
index 18c9c9d..e719b0e 100755
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/AutoPreviewExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/AutoPreviewExtenderImpl.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.os.Build;
 import android.util.Pair;
 import android.util.Size;
@@ -146,4 +147,9 @@
 
         return captureStage;
     }
+
+    @Override
+    public int onSessionType() {
+        return SessionConfiguration.SESSION_REGULAR;
+    }
 }
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
index 4257f2c..0d74482 100755
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
@@ -22,6 +22,7 @@
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.media.Image;
 import android.media.ImageWriter;
@@ -84,6 +85,7 @@
         return CameraCharacteristicAvailability.isEffectAvailable(cameraCharacteristics, EFFECT);
     }
 
+    @NonNull
     @Override
     public List<CaptureStageImpl> getCaptureStages() {
         // Placeholder set of CaptureRequest.Key values
@@ -94,6 +96,7 @@
         return captureStages;
     }
 
+    @Nullable
     @Override
     public CaptureProcessorImpl getCaptureProcessor() {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@@ -115,6 +118,7 @@
 
     }
 
+    @Nullable
     @Override
     public CaptureStageImpl onPresetSession() {
         // The CaptureRequest parameters will be set via SessionConfiguration#setSessionParameters
@@ -132,6 +136,12 @@
     }
 
     @Override
+    public int onSessionType() {
+        return SessionConfiguration.SESSION_REGULAR;
+    }
+
+    @Nullable
+    @Override
     public CaptureStageImpl onEnableSession() {
         // Set the necessary CaptureRequest parameters via CaptureStage, here we use some
         // placeholder set of CaptureRequest.Key values
@@ -141,6 +151,7 @@
         return captureStage;
     }
 
+    @Nullable
     @Override
     public CaptureStageImpl onDisableSession() {
         // Set the necessary CaptureRequest parameters via CaptureStage, here we use some
@@ -156,6 +167,7 @@
         return 3;
     }
 
+    @Nullable
     @Override
     public List<Pair<Integer, Size[]>> getSupportedResolutions() {
         List<Pair<Integer, Size[]>> formatResolutionsPairList = new ArrayList<>();
@@ -188,6 +200,28 @@
         return new Range<>(300L, 1000L);
     }
 
+    @Nullable
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(@NonNull Size captureSize) {
+        return null;
+    }
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        return false;
+    }
+
+    @Nullable
+    @Override
+    public Pair<Long, Long> getRealtimeCaptureLatency() {
+        return null;
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        return false;
+    }
+
     @RequiresApi(23)
     static final class BeautyImageCaptureExtenderCaptureProcessorImpl implements
             CaptureProcessorImpl {
@@ -199,7 +233,7 @@
         }
 
         @Override
-        public void process(Map<Integer, Pair<Image, TotalCaptureResult>> results) {
+        public void process(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results) {
             Log.d(TAG, "Started beauty CaptureProcessor");
 
             Pair<Image, TotalCaptureResult> result = results.get(DEFAULT_STAGE_ID);
@@ -228,8 +262,9 @@
         }
 
         @Override
-        public void process(Map<Integer, Pair<Image, TotalCaptureResult>> results,
-                ProcessResultImpl resultCallback, Executor executor) {
+        public void process(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
+                @NonNull ProcessResultImpl resultCallback, @Nullable Executor executor) {
+            process(results);
         }
 
         @Override
@@ -241,6 +276,23 @@
         public void onImageFormatUpdate(int imageFormat) {
 
         }
+
+        @Override
+        public void onPostviewOutputSurface(@NonNull Surface surface) {
+
+        }
+
+        @Override
+        public void onResolutionUpdate(@NonNull Size size, @NonNull Size postviewSize) {
+
+        }
+
+        @Override
+        public void processWithPostview(
+                @NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
+                @NonNull ProcessResultImpl resultCallback, @Nullable Executor executor) {
+            throw new UnsupportedOperationException("Postview is not supported");
+        }
     }
 
     @NonNull
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/BeautyPreviewExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/BeautyPreviewExtenderImpl.java
index 1665c36..d45d71e 100755
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/BeautyPreviewExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/BeautyPreviewExtenderImpl.java
@@ -19,6 +19,7 @@
 import android.graphics.ImageFormat;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.os.Build;
 import android.util.Pair;
@@ -168,4 +169,9 @@
 
         return captureStage;
     }
+
+    @Override
+    public int onSessionType() {
+        return SessionConfiguration.SESSION_REGULAR;
+    }
 }
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
index fdb7e01..962d4bf 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
@@ -21,6 +21,7 @@
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.media.Image;
 import android.media.ImageWriter;
 import android.os.Build;
@@ -79,6 +80,7 @@
         return CameraCharacteristicAvailability.isEffectAvailable(cameraCharacteristics, EFFECT);
     }
 
+    @NonNull
     @Override
     public List<CaptureStageImpl> getCaptureStages() {
         // Placeholder set of CaptureRequest.Key values
@@ -89,6 +91,7 @@
         return captureStages;
     }
 
+    @Nullable
     @Override
     public CaptureProcessorImpl getCaptureProcessor() {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@@ -110,6 +113,7 @@
 
     }
 
+    @Nullable
     @Override
     public CaptureStageImpl onPresetSession() {
         // The CaptureRequest parameters will be set via SessionConfiguration#setSessionParameters
@@ -126,6 +130,7 @@
         return captureStage;
     }
 
+    @Nullable
     @Override
     public CaptureStageImpl onEnableSession() {
         // Set the necessary CaptureRequest parameters via CaptureStage, here we use some
@@ -136,6 +141,7 @@
         return captureStage;
     }
 
+    @Nullable
     @Override
     public CaptureStageImpl onDisableSession() {
         // Set the necessary CaptureRequest parameters via CaptureStage, here we use some
@@ -151,6 +157,7 @@
         return 3;
     }
 
+    @Nullable
     @Override
     public List<Pair<Integer, Size[]>> getSupportedResolutions() {
         return null;
@@ -162,6 +169,33 @@
         return new Range<>(300L, 1000L);
     }
 
+    @Override
+    public int onSessionType() {
+        return SessionConfiguration.SESSION_REGULAR;
+    }
+
+    @Nullable
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(@NonNull Size captureSize) {
+        return null;
+    }
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        return false;
+    }
+
+    @Nullable
+    @Override
+    public Pair<Long, Long> getRealtimeCaptureLatency() {
+        return null;
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        return false;
+    }
+
     @RequiresApi(23)
     static final class BokehImageCaptureExtenderCaptureProcessorImpl implements
             CaptureProcessorImpl {
@@ -173,7 +207,7 @@
         }
 
         @Override
-        public void process(Map<Integer, Pair<Image, TotalCaptureResult>> results) {
+        public void process(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results) {
             Log.d(TAG, "Started bokeh CaptureProcessor");
 
             Pair<Image, TotalCaptureResult> result = results.get(DEFAULT_STAGE_ID);
@@ -202,9 +236,9 @@
         }
 
         @Override
-        public void process(Map<Integer, Pair<Image, TotalCaptureResult>> results,
-                ProcessResultImpl resultCallback, Executor executor) {
-
+        public void process(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
+                @NonNull ProcessResultImpl resultCallback, @Nullable Executor executor) {
+            process(results);
         }
 
         @Override
@@ -216,6 +250,23 @@
         public void onImageFormatUpdate(int imageFormat) {
 
         }
+
+        @Override
+        public void onPostviewOutputSurface(@NonNull Surface surface) {
+
+        }
+
+        @Override
+        public void onResolutionUpdate(@NonNull Size size, @NonNull Size postviewSize) {
+
+        }
+
+        @Override
+        public void processWithPostview(
+                @NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
+                @NonNull ProcessResultImpl resultCallback, @Nullable Executor executor) {
+            throw new UnsupportedOperationException("Postview is not supported");
+        }
     }
 
     @NonNull
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java
index 99fb8d9..d93e393 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/BokehPreviewExtenderImpl.java
@@ -19,6 +19,7 @@
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.os.Build;
 import android.util.Pair;
 import android.util.Size;
@@ -186,4 +187,9 @@
 
         return captureStage;
     }
+
+    @Override
+    public int onSessionType() {
+        return SessionConfiguration.SESSION_REGULAR;
+    }
 }
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java
index 8518283..2e27560 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/CaptureProcessorImpl.java
@@ -21,9 +21,11 @@
 import android.hardware.camera2.TotalCaptureResult;
 import android.media.Image;
 import android.util.Pair;
+import android.util.Size;
 import android.view.Surface;
 
-import androidx.annotation.RequiresApi;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import java.util.Map;
 import java.util.concurrent.Executor;
@@ -34,7 +36,6 @@
  * @since 1.0
  */
 @SuppressLint("UnknownNullness")
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public interface CaptureProcessorImpl extends ProcessorImpl {
     /**
      * Process a set images captured that were requested.
@@ -46,7 +47,30 @@
      *                process. The {@link Image} that are contained within the map will become
      *                invalid after this method completes, so no references to them should be kept.
      */
-    void process(Map<Integer, Pair<Image, TotalCaptureResult>> results);
+    void process(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results);
+
+    /**
+     * Informs the CaptureProcessorImpl where it should write the postview output to.
+     * This will only be invoked once if a valid postview surface was set.
+     *
+     * @param surface A valid {@link ImageFormat#YUV_420_888} {@link Surface}
+     *                that the CaptureProcessorImpl should write data into.
+     * @since 1.4
+     */
+    void onPostviewOutputSurface(@NonNull Surface surface);
+
+    /**
+     * Invoked when the Camera Framework changes the configured output resolution for
+     * still capture and postview.
+     *
+     * <p>After this call, {@link CaptureProcessorImpl} should expect any {@link Image} received as
+     * input for still capture and postview to be at the specified resolutions.
+     *
+     * @param size for the surface for still capture.
+     * @param postviewSize for the surface for postview.
+     * @since 1.4
+     */
+    void onResolutionUpdate(@NonNull Size size, @NonNull Size postviewSize);
 
     /**
      * Process a set images captured that were requested.
@@ -64,6 +88,32 @@
      *                       run on any arbitrary executor.
      * @since 1.3
      */
-    void process(Map<Integer, Pair<Image, TotalCaptureResult>> results,
-            ProcessResultImpl resultCallback, Executor executor);
+    void process(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
+            @NonNull ProcessResultImpl resultCallback, @Nullable Executor executor);
+
+    /**
+     * Process a set images captured that were requested for both postview and
+     * still capture.
+     *
+     * <p> This processing method will be called if a postview was requested, therefore the
+     * processed postview should be written to the
+     * {@link Surface} received by {@link #onPostviewOutputSurface(Surface, int)}.
+     * The final result of the processing step should be written to the {@link Surface} that was
+     * received by {@link #onOutputSurface(Surface, int)}. Since postview should be available
+     * before the capture, it should be processed and written to the surface before
+     * the final capture is processed.
+     *
+     * @param results             The map of {@link ImageFormat#YUV_420_888} format images and
+     *                            metadata to process. The {@link Image} that are contained within
+     *                            the map will become invalid after this method completes, so no
+     *                            references to them should be kept.
+     * @param resultCallback      Capture result callback to be called once the capture result
+     *                            values of the processed image are ready.
+     * @param executor            The executor to run the callback on. If null then the callback
+     *                            will run on any arbitrary executor.
+     * @throws RuntimeException   if postview feature is not supported
+     * @since 1.4
+     */
+    void processWithPostview(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
+            @NonNull ProcessResultImpl resultCallback, @Nullable Executor executor);
 }
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/CaptureStageImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/CaptureStageImpl.java
index fc86020..c4f4a47 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/CaptureStageImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/CaptureStageImpl.java
@@ -20,7 +20,6 @@
 import android.util.Pair;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
 
 import java.util.List;
 
@@ -29,7 +28,6 @@
  *
  * @since 1.0
  */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public interface CaptureStageImpl {
     /** Returns the identifier for the {@link CaptureStageImpl}. */
     int getId();
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtenderStateListener.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtenderStateListener.java
index b13f602..2d97454 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtenderStateListener.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtenderStateListener.java
@@ -17,7 +17,6 @@
 package androidx.camera.extensions.impl;
 
 import android.content.Context;
-import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CaptureRequest;
@@ -25,14 +24,12 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 
 /**
  * Provides interfaces that the OEM needs to implement to handle the state change.
  *
  * @since 1.0
  */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public interface ExtenderStateListener {
 
     /**
@@ -40,6 +37,7 @@
      * where the use case is started and would be able to allocate resources here. After onInit() is
      * called, the camera ID, cameraCharacteristics and context will not change until onDeInit()
      * has been called.
+     *
      * @param cameraId The camera2 id string of the camera.
      * @param cameraCharacteristics The {@link CameraCharacteristics} of the camera.
      * @param context The {@link Context} used for CameraX.
@@ -56,12 +54,11 @@
 
     /**
      * This will be invoked before creating a
-     * {@link CameraCaptureSession}. The {@link CaptureRequest}
+     * {@link android.hardware.camera2.CameraCaptureSession}. The {@link CaptureRequest}
      * parameters returned via {@link CaptureStageImpl} will be passed to the camera device as
      * part of the capture session initialization via
-     * {@link SessionConfiguration#setSessionParameters(CaptureRequest)} which only supported
-     * from API level 28. The valid parameter is a subset of the available capture request
-     * parameters.
+     * {@link SessionConfiguration#setSessionParameters(CaptureRequest)} which only supported from
+     * API level 28. The valid parameter is a subset of the available capture request parameters.
      *
      * @return The request information to set the session wide camera parameters.
      */
@@ -69,7 +66,7 @@
     CaptureStageImpl onPresetSession();
 
     /**
-     * This will be invoked once after the {@link CameraCaptureSession}
+     * This will be invoked once after the {@link android.hardware.camera2.CameraCaptureSession}
      * has been created. The {@link CaptureRequest} parameters returned via
      * {@link CaptureStageImpl} will be used to generate a single request to the current
      * configured {@link CameraDevice}. The generated request will be submitted to camera before
@@ -81,7 +78,7 @@
     CaptureStageImpl onEnableSession();
 
     /**
-     * This will be invoked before the {@link CameraCaptureSession} is
+     * This will be invoked before the {@link android.hardware.camera2.CameraCaptureSession} is
      * closed. The {@link CaptureRequest} parameters returned via {@link CaptureStageImpl} will
      * be used to generate a single request to the currently configured {@link CameraDevice}. The
      * generated request will be submitted to camera before the CameraCaptureSession is closed.
@@ -90,4 +87,21 @@
      */
     @Nullable
     CaptureStageImpl onDisableSession();
+
+    /**
+     * This will be invoked before the {@link android.hardware.camera2.CameraCaptureSession} is
+     * initialized and must return a valid camera session type
+     * {@link android.hardware.camera2.params.SessionConfiguration#getSessionType}
+     * to be used to configure camera capture session. Both the preview and the image capture
+     * extender must return the same session type value for a specific extension type. If there
+     * is inconsistency between the session type values from preview and image extenders, then
+     * the session configuration will fail.
+     *
+     * @return Camera capture session type. Regular and vendor specific types are supported but
+     * not high speed values. The extension can return -1 in which case the camera capture session
+     * will be configured to use the default regular type.
+     *
+     * @since 1.4
+     */
+    int onSessionType();
 }
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtensionVersionImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtensionVersionImpl.java
index 212bea0..5235e59 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtensionVersionImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtensionVersionImpl.java
@@ -32,7 +32,7 @@
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public class ExtensionVersionImpl {
     private static final String TAG = "ExtenderVersionImpl";
-    private static final String VERSION = "1.2.0";
+    private static final String VERSION = "1.4.0";
 
     public ExtensionVersionImpl() {
     }
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtensionsTestlibControl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtensionsTestlibControl.java
new file mode 100644
index 0000000..6cf35b5
--- /dev/null
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtensionsTestlibControl.java
@@ -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.camera.extensions.impl;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+/**
+ * An internal utility class that allows tests to specify whether to enable basic extender or
+ * advanced extender of this testlib.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class ExtensionsTestlibControl {
+    public enum ImplementationType {
+        ADVANCED_EXTENDER,
+        BASIC_EXTENDER
+    }
+
+    private static ExtensionsTestlibControl sInstance;
+    private static Object sLock = new Object();
+
+    private ExtensionsTestlibControl() {
+    }
+
+    /**
+     * Gets the singleton instance.
+     */
+    @NonNull
+    public static ExtensionsTestlibControl getInstance() {
+        synchronized (sLock) {
+            if (sInstance == null) {
+                sInstance = new ExtensionsTestlibControl();
+            }
+            return sInstance;
+        }
+    }
+
+    private ImplementationType mImplementationType = ImplementationType.BASIC_EXTENDER;
+
+    /**
+     * Set the implementation type.
+     */
+    public void setImplementationType(@NonNull ImplementationType type) {
+        mImplementationType = type;
+    }
+
+    /**
+     * Gets the implementation type;
+     */
+    @NonNull
+    public ImplementationType getImplementationType() {
+        return mImplementationType;
+    }
+}
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
index 6a16dff..0ba4752 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
@@ -19,10 +19,12 @@
 
 import android.annotation.SuppressLint;
 import android.content.Context;
+import android.graphics.ImageFormat;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.media.Image;
 import android.media.ImageWriter;
 import android.os.Build;
@@ -38,6 +40,7 @@
 
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Executor;
@@ -106,6 +109,7 @@
         return false;
     }
 
+    @NonNull
     @Override
     public List<CaptureStageImpl> getCaptureStages() {
         // Under exposed capture stage
@@ -133,6 +137,8 @@
         return captureStages;
     }
 
+
+    @Nullable
     @Override
     public CaptureProcessorImpl getCaptureProcessor() {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@@ -154,6 +160,7 @@
 
     }
 
+    @Nullable
     @Override
     public CaptureStageImpl onPresetSession() {
         // The CaptureRequest parameters will be set via SessionConfiguration#setSessionParameters
@@ -166,12 +173,14 @@
         return captureStage;
     }
 
+    @Nullable
     @Override
     public CaptureStageImpl onEnableSession() {
         SettableCaptureStage captureStage = new SettableCaptureStage(SESSION_STAGE_ID);
         return captureStage;
     }
 
+    @Nullable
     @Override
     public CaptureStageImpl onDisableSession() {
         SettableCaptureStage captureStage = new SettableCaptureStage(SESSION_STAGE_ID);
@@ -183,6 +192,7 @@
         return 4;
     }
 
+    @Nullable
     @Override
     public List<Pair<Integer, Size[]>> getSupportedResolutions() {
         return null;
@@ -197,6 +207,7 @@
     @RequiresApi(23)
     static final class HdrImageCaptureExtenderCaptureProcessorImpl implements CaptureProcessorImpl {
         private ImageWriter mImageWriter;
+        private Surface mPostViewSurface;
 
         @Override
         public void onOutputSurface(@NonNull Surface surface, int imageFormat) {
@@ -204,9 +215,23 @@
         }
 
         @Override
-        public void process(Map<Integer, Pair<Image, TotalCaptureResult>> results) {
+        public void process(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results) {
+            processInternal(results, null, null, false);
+        }
+
+        @Override
+        public void process(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
+                @NonNull ProcessResultImpl resultCallback, @Nullable Executor executor) {
+            processInternal(results, resultCallback, executor, false);
+        }
+
+        public void processInternal(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
+                @Nullable ProcessResultImpl resultCallback, @Nullable Executor executor,
+                boolean hasPostview) {
             Log.d(TAG, "Started HDR CaptureProcessor");
 
+            Executor executorForCallback = executor != null ? executor : (cmd) -> cmd.run();
+
             // Check for availability of all requested images
             if (!results.containsKey(UNDER_STAGE_ID)) {
                 Log.w(TAG,
@@ -238,6 +263,9 @@
             // Do processing here
             // The sample here simply returns the normal image result
             Image normalImage = imageDataPairs.get(NORMAL_STAGE_ID).first;
+            if (hasPostview) {
+                YuvToJpegConverter.writeYuvToJpegSurface(normalImage, mPostViewSurface);
+            }
 
             if (outputImage.getWidth() != normalImage.getWidth()
                     || outputImage.getHeight() != normalImage.getHeight()) {
@@ -248,6 +276,12 @@
                         outputImage.getHeight()));
             }
 
+            if (resultCallback != null) {
+                executorForCallback.execute(() -> {
+                    resultCallback.onCaptureProcessProgressed(10);
+                });
+            }
+
             try {
                 // copy y plane
                 Image.Plane inYPlane = normalImage.getPlanes()[0];
@@ -266,6 +300,11 @@
                     }
                 }
 
+                if (resultCallback != null) {
+                    executorForCallback.execute(
+                            () -> resultCallback.onCaptureProcessProgressed(50));
+                }
+
                 // Copy UV
                 for (int i = 1; i < 3; i++) {
                     Image.Plane inPlane = normalImage.getPlanes()[i];
@@ -294,16 +333,35 @@
             }
 
             mImageWriter.queueInputImage(outputImage);
+            if (resultCallback != null) {
+                executorForCallback.execute(
+                        () -> resultCallback.onCaptureProcessProgressed(100));
+            }
+
+            TotalCaptureResult captureResult = results.get(NORMAL_STAGE_ID).second;
+
+            if (resultCallback != null) {
+                executorForCallback.execute(
+                        () -> resultCallback.onCaptureCompleted(
+                                captureResult.get(CaptureResult.SENSOR_TIMESTAMP),
+                                getFilteredResults(captureResult)));
+            }
 
             Log.d(TAG, "Completed HDR CaptureProcessor");
         }
 
-        @Override
-        public void process(Map<Integer, Pair<Image, TotalCaptureResult>> results,
-                ProcessResultImpl resultCallback, Executor executor) {
+        @SuppressWarnings("unchecked")
+        private List<Pair<CaptureResult.Key, Object>> getFilteredResults(
+                TotalCaptureResult captureResult) {
+            List<Pair<CaptureResult.Key, Object>> list = new ArrayList<>();
+            for (CaptureResult.Key key : captureResult.getKeys()) {
+                list.add(new Pair<>(key, captureResult.get(key)));
+            }
 
+            return list;
         }
 
+
         @Override
         public void onResolutionUpdate(@NonNull Size size) {
 
@@ -313,6 +371,24 @@
         public void onImageFormatUpdate(int imageFormat) {
 
         }
+
+        @Override
+        public void onPostviewOutputSurface(@NonNull Surface surface) {
+            mPostViewSurface = surface;
+        }
+
+        @Override
+        public void onResolutionUpdate(@NonNull Size size, @NonNull Size postviewSize) {
+            onResolutionUpdate(size);
+        }
+
+        @Override
+        public void processWithPostview(
+                @NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
+                @NonNull ProcessResultImpl resultCallback, @Nullable Executor executor) {
+            Log.d(TAG, "processWithPostview");
+            processInternal(results, resultCallback, executor, true);
+        }
     }
 
     @NonNull
@@ -321,6 +397,34 @@
         return null;
     }
 
+    @Override
+    public int onSessionType() {
+        return SessionConfiguration.SESSION_REGULAR;
+    }
+
+    @Nullable
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(@NonNull Size captureSize) {
+        Pair<Integer, Size[]> pair = new Pair<>(ImageFormat.JPEG, new Size[] {captureSize});
+        return Arrays.asList(pair);
+    }
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        return true;
+    }
+
+    @Nullable
+    @Override
+    public Pair<Long, Long> getRealtimeCaptureLatency() {
+        return null;
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        return true;
+    }
+
     @NonNull
     @Override
     public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java
index defe8e9..60d2888 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.media.Image;
 import android.os.Build;
 import android.os.Handler;
@@ -47,6 +48,8 @@
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public final class HdrPreviewExtenderImpl implements PreviewExtenderImpl {
+    private static final String TAG = "HdrPreviewExtenderImpl";
+
     private static final int DEFAULT_STAGE_ID = 0;
 
     @Nullable
@@ -174,7 +177,7 @@
         @Override
         public void process(Image image, TotalCaptureResult result,
                 ProcessResultImpl resultCallback, Executor executor) {
-
+            process(image, result);
         }
 
         @Override
@@ -225,4 +228,9 @@
             }
         }
     }
+
+    @Override
+    public int onSessionType() {
+        return SessionConfiguration.SESSION_REGULAR;
+    }
 }
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
index f235fbe..dbcd1ef 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ImageCaptureExtenderImpl.java
@@ -20,14 +20,12 @@
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
-import android.hardware.camera2.params.StreamConfigurationMap;
 import android.util.Pair;
 import android.util.Range;
 import android.util.Size;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 
 import java.util.List;
 
@@ -36,7 +34,6 @@
  *
  * @since 1.0
  */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public interface ImageCaptureExtenderImpl extends ExtenderStateListener {
     /**
      * Indicates whether the extension is supported on the device.
@@ -66,7 +63,7 @@
     CaptureProcessorImpl getCaptureProcessor();
 
     /** The set of captures that are needed to create an image with the effect. */
-    @Nullable
+    @NonNull
     List<CaptureStageImpl> getCaptureStages();
 
     /**
@@ -81,18 +78,31 @@
      * <p>Pair list composed with {@link ImageFormat} and {@link Size} array will be returned.
      *
      * <p>The returned resolutions should be subset of the supported sizes retrieved from
-     * {@link StreamConfigurationMap} for the camera device. If the
+     * {@link android.hardware.camera2.params.StreamConfigurationMap} for the camera device. If the
      * returned list is not null, it will be used to find the best resolutions combination for
      * the bound use cases.
      *
      * @return the customized supported resolutions, or null to support all sizes retrieved from
-     * {@link StreamConfigurationMap}.
+     *         {@link android.hardware.camera2.params.StreamConfigurationMap}.
      * @since 1.1
      */
     @Nullable
     List<Pair<Integer, Size[]>> getSupportedResolutions();
 
     /**
+     * Returns supported output format/size map for postview image. OEM is required to support
+     * both JPEG and YUV_420_888 format output.
+     *
+     * <p>Pair list composed with {@link ImageFormat} and {@link Size} array will be returned.
+     * The sizes must be smaller than or equal to the provided capture size and have the same
+     * aspect ratio as the given capture size.
+     *
+     * @since 1.4
+     */
+    @Nullable
+    List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(@NonNull Size captureSize);
+
+    /**
      * Returns the estimated capture latency range in milliseconds for the target capture
      * resolution.
      *
@@ -172,6 +182,15 @@
     List<CaptureResult.Key> getAvailableCaptureResultKeys();
 
     /**
+     * Advertise support for {@link ProcessResultImpl#onCaptureProcessProgressed}.
+     *
+     * @return {@code true} in case the process progress callback is supported and is expected to
+     * be triggered, {@code false} otherwise.
+     * @since 1.4
+     */
+    boolean isCaptureProcessProgressAvailable();
+
+    /**
      * Returns the dynamically calculated capture latency pair in milliseconds.
      *
      * <p>In contrast to {@link #getEstimatedCaptureLatencyRange} this method is guaranteed to be
@@ -190,7 +209,16 @@
      * @since 1.4
      */
     @Nullable
-    default Pair<Long, Long> getRealtimeCaptureLatency() {
-        return null;
-    };
+    Pair<Long, Long> getRealtimeCaptureLatency();
+
+    /**
+     * Indicates whether the extension supports the postview for still capture feature.
+     * If the extension is using HAL processing, false should be returned since the
+     * postview feature is not currently supported for this case.
+     *
+     * @return {@code true} in case postview for still capture is supported
+     * {@code false} otherwise.
+     * @since 1.4
+     */
+    boolean isPostviewAvailable();
 }
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
index 98c171e..b3e38b7 100755
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
@@ -21,6 +21,7 @@
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.media.Image;
 import android.media.ImageWriter;
 import android.os.Build;
@@ -79,6 +80,7 @@
         return CameraCharacteristicAvailability.isEffectAvailable(cameraCharacteristics, EFFECT);
     }
 
+    @NonNull
     @Override
     public List<CaptureStageImpl> getCaptureStages() {
         // Placeholder set of CaptureRequest.Key values
@@ -89,6 +91,7 @@
         return captureStages;
     }
 
+    @Nullable
     @Override
     public CaptureProcessorImpl getCaptureProcessor() {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@@ -110,6 +113,7 @@
 
     }
 
+    @Nullable
     @Override
     public CaptureStageImpl onPresetSession() {
         // The CaptureRequest parameters will be set via SessionConfiguration#setSessionParameters
@@ -126,6 +130,7 @@
         return captureStage;
     }
 
+    @Nullable
     @Override
     public CaptureStageImpl onEnableSession() {
         // Set the necessary CaptureRequest parameters via CaptureStage, here we use some
@@ -136,6 +141,7 @@
         return captureStage;
     }
 
+    @Nullable
     @Override
     public CaptureStageImpl onDisableSession() {
         // Set the necessary CaptureRequest parameters via CaptureStage, here we use some
@@ -151,6 +157,7 @@
         return 3;
     }
 
+    @Nullable
     @Override
     public List<Pair<Integer, Size[]>> getSupportedResolutions() {
         return null;
@@ -163,6 +170,33 @@
         return new Range<>(300L, 1000L);
     }
 
+    @Override
+    public int onSessionType() {
+        return SessionConfiguration.SESSION_REGULAR;
+    }
+
+    @Nullable
+    @Override
+    public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(@NonNull Size captureSize) {
+        return null;
+    }
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        return false;
+    }
+
+    @Nullable
+    @Override
+    public Pair<Long, Long> getRealtimeCaptureLatency() {
+        return null;
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        return false;
+    }
+
     @RequiresApi(23)
     static final class NightImageCaptureExtenderCaptureProcessorImpl
             implements CaptureProcessorImpl {
@@ -174,7 +208,7 @@
         }
 
         @Override
-        public void process(Map<Integer, Pair<Image, TotalCaptureResult>> results) {
+        public void process(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results) {
             Log.d(TAG, "Started night CaptureProcessor");
 
             Pair<Image, TotalCaptureResult> result = results.get(DEFAULT_STAGE_ID);
@@ -203,8 +237,9 @@
         }
 
         @Override
-        public void process(Map<Integer, Pair<Image, TotalCaptureResult>> results,
-                ProcessResultImpl resultCallback, Executor executor) {
+        public void process(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
+                @NonNull ProcessResultImpl resultCallback, @Nullable Executor executor) {
+            process(results);
         }
 
         @Override
@@ -214,6 +249,23 @@
         @Override
         public void onImageFormatUpdate(int imageFormat) {
         }
+
+        @Override
+        public void onPostviewOutputSurface(@NonNull Surface surface) {
+
+        }
+
+        @Override
+        public void onResolutionUpdate(@NonNull Size size, @NonNull Size postviewSize) {
+
+        }
+
+        @Override
+        public void processWithPostview(
+                @NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
+                @NonNull ProcessResultImpl resultCallback, @Nullable Executor executor) {
+            throw new UnsupportedOperationException("Postview is not supported");
+        }
     }
 
     @NonNull
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/NightPreviewExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/NightPreviewExtenderImpl.java
index 46512ee..754a9f5 100755
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/NightPreviewExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/NightPreviewExtenderImpl.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.SessionConfiguration;
 import android.os.Build;
 import android.util.Pair;
 import android.util.Size;
@@ -146,4 +147,9 @@
 
         return captureStage;
     }
+
+    @Override
+    public int onSessionType() {
+        return SessionConfiguration.SESSION_REGULAR;
+    }
 }
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/NoOpCaptureProcessorImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/NoOpCaptureProcessorImpl.java
index a84fed7..a78fa30 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/NoOpCaptureProcessorImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/NoOpCaptureProcessorImpl.java
@@ -23,6 +23,7 @@
 import android.view.Surface;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 
 import java.util.Map;
@@ -51,8 +52,23 @@
     }
 
     @Override
-    public void process(Map<Integer, Pair<Image, TotalCaptureResult>> results,
-            ProcessResultImpl resultCallback, Executor executor) {
+    public void process(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
+            @NonNull ProcessResultImpl resultCallback, @Nullable Executor executor) {
+    }
+
+    @Override
+    public void onPostviewOutputSurface(@NonNull Surface surface) {
+
+    }
+
+    @Override
+    public void onResolutionUpdate(@NonNull Size size, @NonNull Size postviewSize) {
+
+    }
+
+    @Override
+    public void processWithPostview(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
+            @NonNull ProcessResultImpl resultCallback, @Nullable Executor executor) {
 
     }
 }
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java
index 93cbcb9..d921151 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/PreviewExtenderImpl.java
@@ -18,14 +18,12 @@
 
 import android.graphics.ImageFormat;
 import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.TotalCaptureResult;
 import android.util.Pair;
 import android.util.Size;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 
 import java.util.List;
 
@@ -34,7 +32,6 @@
  *
  * @since 1.0
  */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public interface PreviewExtenderImpl extends ExtenderStateListener {
     /** The different types of the preview processing. */
     enum ProcessorType {
@@ -61,7 +58,8 @@
      *
      * <p>This should be called before any other method on the extender. The exception is {@link
      * #isExtensionAvailable(String, CameraCharacteristics)}.
-     *  @param cameraId The camera2 id string of the camera.
+     *
+     * @param cameraId The camera2 id string of the camera.
      * @param cameraCharacteristics The {@link CameraCharacteristics} of the camera.
      */
     void init(@NonNull String cameraId, @NonNull CameraCharacteristics cameraCharacteristics);
@@ -70,7 +68,7 @@
      * The set of parameters required to produce the effect on the preview stream.
      *
      * <p> This will be the initial set of parameters used for the preview
-     * {@link CaptureRequest}. If the {@link ProcessorType} is defined as
+     * {@link android.hardware.camera2.CaptureRequest}. If the {@link ProcessorType} is defined as
      * {@link ProcessorType#PROCESSOR_TYPE_REQUEST_UPDATE_ONLY} then this will be updated when
      * the {@link RequestUpdateProcessorImpl#process(TotalCaptureResult)} from {@link
      * #getProcessor()} has been called, this should be updated to reflect the new {@link
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/PreviewImageProcessorImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/PreviewImageProcessorImpl.java
index ff6ed88..c4bf2c8 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/PreviewImageProcessorImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/PreviewImageProcessorImpl.java
@@ -16,12 +16,12 @@
 
 package androidx.camera.extensions.impl;
 
-import android.annotation.SuppressLint;
 import android.graphics.ImageFormat;
 import android.hardware.camera2.TotalCaptureResult;
 import android.media.Image;
 
-import androidx.annotation.RequiresApi;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import java.util.concurrent.Executor;
 
@@ -31,8 +31,6 @@
  *
  * @since 1.0
  */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-@SuppressLint("UnknownNullness")
 public interface PreviewImageProcessorImpl extends ProcessorImpl {
     /**
      * Processes the requested image capture.
@@ -44,7 +42,7 @@
      *               invalid after the method completes so no reference to it should be kept.
      * @param result The metadata associated with the image to process.
      */
-    void process(Image image, TotalCaptureResult result);
+    void process(@NonNull Image image, @NonNull TotalCaptureResult result);
 
     /**
      * Processes the requested image capture.
@@ -62,6 +60,7 @@
      *                       run on any arbitrary executor.
      * @since 1.3
      */
-    void process(Image image, TotalCaptureResult result, ProcessResultImpl resultCallback,
-            Executor executor);
+    void process(@NonNull Image image, @NonNull TotalCaptureResult result,
+            @NonNull ProcessResultImpl resultCallback,
+            @Nullable Executor executor);
 }
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ProcessResultImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ProcessResultImpl.java
index d0e3605..0cc10c5 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ProcessResultImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ProcessResultImpl.java
@@ -16,18 +16,17 @@
 
 package androidx.camera.extensions.impl;
 
-import android.annotation.SuppressLint;
 import android.hardware.camera2.CaptureResult;
 import android.util.Pair;
 
+import androidx.annotation.NonNull;
+
 import java.util.List;
 
 /**
  * Allows clients to receive information about the capture result values of processed frames.
  *
- * @since 1.3
  */
-@SuppressLint("UnknownNullness")
 public interface ProcessResultImpl {
     /**
      * Capture result callback that needs to be called when the process capture results are
@@ -40,6 +39,23 @@
      *                             must also be passed as part of this callback. Both Camera2 and
      *                             CameraX guarantee that those two settings and results are always
      *                             supported and applied by the corresponding framework.
+     * @since 1.3
      */
-    void onCaptureCompleted(long shutterTimestamp, List<Pair<CaptureResult.Key, Object>> result);
+    void onCaptureCompleted(long shutterTimestamp,
+            @NonNull List<Pair<CaptureResult.Key, Object>> result);
+
+    /**
+     * Capture progress callback that needs to be called when the process capture is
+     * ongoing and includes the estimated progress of the processing.
+     *
+     * <p>Extensions must ensure that they always call this callback with monotonically increasing
+     * values.</p>
+     *
+     * <p>Extensions are allowed to trigger this callback multiple times but at the minimum the
+     * callback is expected to be called once when processing is done with value 100.</p>
+     *
+     * @param progress             Value between 0 and 100.
+     * @since 1.4
+     */
+    default void onCaptureProcessProgressed(int progress) {}
 }
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ProcessorImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ProcessorImpl.java
index a339cca..e5ca19e 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ProcessorImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ProcessorImpl.java
@@ -20,15 +20,12 @@
 import android.view.Surface;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
 
 /**
  * Processes an input image stream and produces an output image stream.
  *
  * @since 1.0
  */
-@SuppressWarnings("unused")
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public interface ProcessorImpl {
     /**
      * Updates where the ProcessorImpl should write the output to.
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/RequestUpdateProcessorImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/RequestUpdateProcessorImpl.java
index 8a6a2e2..ac3bfb3 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/RequestUpdateProcessorImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/RequestUpdateProcessorImpl.java
@@ -19,14 +19,12 @@
 import android.hardware.camera2.TotalCaptureResult;
 
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 
 /**
  * Processes a {@link TotalCaptureResult} to update a CaptureStage.
  *
  * @since 1.0
  */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public interface RequestUpdateProcessorImpl extends ProcessorImpl {
     /**
      * Process the {@link TotalCaptureResult} to update the {@link CaptureStageImpl}
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/YuvToJpegConverter.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/YuvToJpegConverter.java
new file mode 100644
index 0000000..0681b97
--- /dev/null
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/YuvToJpegConverter.java
@@ -0,0 +1,101 @@
+/*
+ * 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.impl;
+
+import android.graphics.ImageFormat;
+import android.graphics.Rect;
+import android.graphics.YuvImage;
+import android.media.Image;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.ImageProcessingUtil;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+
+@RequiresApi(23)
+class YuvToJpegConverter {
+    private YuvToJpegConverter() {}
+    public static void writeYuvToJpegSurface(@NonNull Image yuvMediaImage,
+            @NonNull Surface jpegSurface) {
+        byte[] yuvBytes = yuv_420_888toNv21(yuvMediaImage);
+        YuvImage yuvImage = new YuvImage(yuvBytes, ImageFormat.NV21, yuvMediaImage.getWidth(),
+                yuvMediaImage.getHeight(), null);
+        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+        yuvImage.compressToJpeg(
+                new Rect(0, 0, yuvMediaImage.getWidth(), yuvMediaImage.getHeight()),
+                95, byteArrayOutputStream);
+        ImageProcessingUtil.writeJpegBytesToSurface(jpegSurface,
+                byteArrayOutputStream.toByteArray());
+        yuvMediaImage.getPlanes()[0].getBuffer().rewind();
+    }
+
+    @NonNull
+    private static byte[] yuv_420_888toNv21(@NonNull Image image) {
+        Image.Plane yPlane = image.getPlanes()[0];
+        Image.Plane uPlane = image.getPlanes()[1];
+        Image.Plane vPlane = image.getPlanes()[2];
+
+        ByteBuffer yBuffer = yPlane.getBuffer();
+        ByteBuffer uBuffer = uPlane.getBuffer();
+        ByteBuffer vBuffer = vPlane.getBuffer();
+        yBuffer.rewind();
+        uBuffer.rewind();
+        vBuffer.rewind();
+
+        int ySize = yBuffer.remaining();
+
+        int position = 0;
+        byte[] nv21 = new byte[ySize + (image.getWidth() * image.getHeight() / 2)];
+
+        // Add the full y buffer to the array. If rowStride > 1, some padding may be skipped.
+        for (int row = 0; row < image.getHeight(); row++) {
+            yBuffer.get(nv21, position, image.getWidth());
+            position += image.getWidth();
+            yBuffer.position(
+                    Math.min(ySize, yBuffer.position() - image.getWidth() + yPlane.getRowStride()));
+        }
+
+        int chromaHeight = image.getHeight() / 2;
+        int chromaWidth = image.getWidth() / 2;
+        int vRowStride = vPlane.getRowStride();
+        int uRowStride = uPlane.getRowStride();
+        int vPixelStride = vPlane.getPixelStride();
+        int uPixelStride = uPlane.getPixelStride();
+
+        // Interleave the u and v frames, filling up the rest of the buffer. Use two line buffers to
+        // perform faster bulk gets from the byte buffers.
+        byte[] vLineBuffer = new byte[vRowStride];
+        byte[] uLineBuffer = new byte[uRowStride];
+        for (int row = 0; row < chromaHeight; row++) {
+            vBuffer.get(vLineBuffer, 0, Math.min(vRowStride, vBuffer.remaining()));
+            uBuffer.get(uLineBuffer, 0, Math.min(uRowStride, uBuffer.remaining()));
+            int vLineBufferPosition = 0;
+            int uLineBufferPosition = 0;
+            for (int col = 0; col < chromaWidth; col++) {
+                nv21[position++] = vLineBuffer[vLineBufferPosition];
+                nv21[position++] = uLineBuffer[uLineBufferPosition];
+                vLineBufferPosition += vPixelStride;
+                uLineBufferPosition += uPixelStride;
+            }
+        }
+
+        return nv21;
+    }
+}
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/AdvancedExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/AdvancedExtenderImpl.java
index 465bfe8..03c6ba2 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/AdvancedExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/AdvancedExtenderImpl.java
@@ -16,13 +16,14 @@
 
 package androidx.camera.extensions.impl.advanced;
 
-import android.annotation.SuppressLint;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.util.Range;
 import android.util.Size;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.camera.extensions.impl.ExtensionVersionImpl;
 
 import java.util.List;
@@ -50,7 +51,6 @@
  *
  * @since 1.2
  */
-@SuppressLint("UnknownNullness")
 public interface AdvancedExtenderImpl {
 
     /**
@@ -64,8 +64,8 @@
      *                           physical camera ids and their CameraCharacteristics.
      * @return true if the extension is supported, otherwise false
      */
-    boolean isExtensionAvailable(String cameraId,
-            Map<String, CameraCharacteristics> characteristicsMap);
+    boolean isExtensionAvailable(@NonNull String cameraId,
+            @NonNull Map<String, CameraCharacteristics> characteristicsMap);
 
     /**
      * Initializes the extender to be used with the specified camera.
@@ -80,7 +80,8 @@
      *                           If the camera is logical camera, it will also contain associated
      *                           physical camera ids and their CameraCharacteristics.
      */
-    void init(String cameraId, Map<String, CameraCharacteristics> characteristicsMap);
+    void init(@NonNull String cameraId,
+            @NonNull Map<String, CameraCharacteristics> characteristicsMap);
 
     /**
      * Returns the estimated capture latency range in milliseconds for the
@@ -97,8 +98,9 @@
      * @return the range of estimated minimal and maximal capture latency in milliseconds.
      * Returns null if no capture latency info can be provided.
      */
-    Range<Long> getEstimatedCaptureLatencyRange(String cameraId,
-            Size captureOutputSize, int imageFormat);
+    @Nullable
+    Range<Long> getEstimatedCaptureLatencyRange(@NonNull String cameraId,
+            @Nullable Size captureOutputSize, int imageFormat);
 
     /**
      * Returns supported output format/size map for preview. The format could be PRIVATE or
@@ -111,7 +113,8 @@
      * the HAL. Alternatively OEM can configure a intermediate YUV surface of the same size and
      * writes the output to the preview output surface.
      */
-    Map<Integer, List<Size>> getSupportedPreviewOutputResolutions(String cameraId);
+    @NonNull
+    Map<Integer, List<Size>> getSupportedPreviewOutputResolutions(@NonNull String cameraId);
 
     /**
      * Returns supported output format/size map for image capture. OEM is required to support
@@ -121,7 +124,21 @@
      * format/size could be either added in CameraCaptureSession with HAL processing OR it
      * configures intermediate surfaces(YUV/RAW..) and writes the output to the output surface.
      */
-    Map<Integer, List<Size>> getSupportedCaptureOutputResolutions(String cameraId);
+    @NonNull
+    Map<Integer, List<Size>> getSupportedCaptureOutputResolutions(@NonNull String cameraId);
+
+    /**
+     * Returns supported output format/size map for postview image. OEM is required to support
+     * both JPEG and YUV_420_888 format output.
+     *
+     * <p>The returned sizes must be smaller than or equal to the provided capture size and have the
+     * same aspect ratio as the given capture size. If no supported resolution exists for the
+     * provided capture size then an empty map is returned.
+     *
+     * @since 1.4
+     */
+    @NonNull
+    Map<Integer, List<Size>> getSupportedPostviewResolutions(@NonNull Size captureSize);
 
     /**
      * Returns supported output sizes for Image Analysis (YUV_420_888 format).
@@ -130,12 +147,14 @@
      * output surfaces. If imageAnalysis YUV surface is not supported, OEM should return null or
      * empty list.
      */
-    List<Size> getSupportedYuvAnalysisResolutions(String cameraId);
+    @Nullable
+    List<Size> getSupportedYuvAnalysisResolutions(@NonNull String cameraId);
 
     /**
      * Returns a processor for activating extension sessions. It implements all the interactions
      * required for starting a extension and cleanup.
      */
+    @NonNull
     SessionProcessorImpl createSessionProcessor();
 
     /**
@@ -169,6 +188,7 @@
      * are not supported.
      * @since 1.3
      */
+    @NonNull
     List<CaptureRequest.Key> getAvailableCaptureRequestKeys();
 
     /**
@@ -184,5 +204,26 @@
      * an empty list if capture results are not supported.
      * @since 1.3
      */
+    @NonNull
     List<CaptureResult.Key> getAvailableCaptureResultKeys();
+
+    /**
+     * Advertise support for {@link SessionProcessorImpl#onCaptureProcessProgressed}.
+     *
+     * @return {@code true} in case the process progress callback is supported and is expected to
+     * be triggered, {@code false} otherwise.
+     * @since 1.4
+     */
+    boolean isCaptureProcessProgressAvailable();
+
+    /**
+     * Indicates whether the extension supports the postview for still capture feature.
+     * If the extension is using HAL processing, false should be returned since the
+     * postview feature is not currently supported for this case.
+     *
+     * @return {@code true} in case postview for still capture is supported
+     * {@code false} otherwise.
+     * @since 1.4
+     */
+    boolean isPostviewAvailable();
 }
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/AutoAdvancedExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/AutoAdvancedExtenderImpl.java
index 7065a8f..9f567ba 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/AutoAdvancedExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/AutoAdvancedExtenderImpl.java
@@ -16,13 +16,15 @@
 
 package androidx.camera.extensions.impl.advanced;
 
-import android.annotation.SuppressLint;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.util.Range;
 import android.util.Size;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import java.util.List;
 import java.util.Map;
 
@@ -33,58 +35,82 @@
  *
  * @since 1.2
  */
-@SuppressLint("UnknownNullness")
 public class AutoAdvancedExtenderImpl implements AdvancedExtenderImpl {
     public AutoAdvancedExtenderImpl() {
     }
 
     @Override
-    public boolean isExtensionAvailable(String cameraId,
-            Map<String, CameraCharacteristics> characteristicsMap) {
-        return false;
+    public boolean isExtensionAvailable(@NonNull String cameraId,
+            @NonNull Map<String, CameraCharacteristics> characteristicsMap) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
-    public void init(String cameraId,
-            Map<String, CameraCharacteristics> characteristicsMap) {
+    public void init(@NonNull String cameraId,
+            @NonNull Map<String, CameraCharacteristics> characteristicsMap) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @Nullable
     public Range<Long> getEstimatedCaptureLatencyRange(
-            String cameraId, Size size, int imageFormat) {
-        return null;
+            @NonNull String cameraId, @Nullable Size size, int imageFormat) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @NonNull
     public Map<Integer, List<Size>> getSupportedPreviewOutputResolutions(
-            String cameraId) {
-        return null;
+            @NonNull String cameraId) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @NonNull
     public Map<Integer, List<Size>> getSupportedCaptureOutputResolutions(
-            String cameraId) {
-        return null;
+            @NonNull String cameraId) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @NonNull
+    public Map<Integer, List<Size>> getSupportedPostviewResolutions(
+            @NonNull Size captureSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    @Nullable
     public List<Size> getSupportedYuvAnalysisResolutions(
-            String cameraId) {
-        return null;
+            @NonNull String cameraId) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @NonNull
     public SessionProcessorImpl createSessionProcessor() {
-        return null;
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @NonNull
     public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
-        return null;
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @NonNull
     public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
-        return null;
+        throw new RuntimeException("Stub, replace with implementation.");
     }
-}
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/BeautyAdvancedExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/BeautyAdvancedExtenderImpl.java
index b553ed6..40bbb93 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/BeautyAdvancedExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/BeautyAdvancedExtenderImpl.java
@@ -16,13 +16,15 @@
 
 package androidx.camera.extensions.impl.advanced;
 
-import android.annotation.SuppressLint;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.util.Range;
 import android.util.Size;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import java.util.List;
 import java.util.Map;
 
@@ -33,58 +35,82 @@
  *
  * @since 1.2
  */
-@SuppressLint("UnknownNullness")
 public class BeautyAdvancedExtenderImpl implements AdvancedExtenderImpl {
     public BeautyAdvancedExtenderImpl() {
     }
 
     @Override
-    public boolean isExtensionAvailable(String cameraId,
-            Map<String, CameraCharacteristics> characteristicsMap) {
-        return false;
+    public boolean isExtensionAvailable(@NonNull String cameraId,
+            @NonNull Map<String, CameraCharacteristics> characteristicsMap) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
-    public void init(String cameraId,
-            Map<String, CameraCharacteristics> characteristicsMap) {
+    public void init(@NonNull String cameraId,
+            @NonNull Map<String, CameraCharacteristics> characteristicsMap) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @Nullable
     public Range<Long> getEstimatedCaptureLatencyRange(
-            String cameraId, Size size, int imageFormat) {
-        return null;
+            @NonNull String cameraId, @Nullable Size size, int imageFormat) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @NonNull
     public Map<Integer, List<Size>> getSupportedPreviewOutputResolutions(
-            String cameraId) {
-        return null;
+            @NonNull String cameraId) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @NonNull
     public Map<Integer, List<Size>> getSupportedCaptureOutputResolutions(
-            String cameraId) {
-        return null;
+            @NonNull String cameraId) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @NonNull
+    public Map<Integer, List<Size>> getSupportedPostviewResolutions(
+            @NonNull Size captureSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    @Nullable
     public List<Size> getSupportedYuvAnalysisResolutions(
-            String cameraId) {
-        return null;
+            @NonNull String cameraId) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @NonNull
     public SessionProcessorImpl createSessionProcessor() {
-        return null;
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @NonNull
     public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
-        return null;
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @NonNull
     public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
-        return null;
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 }
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/BokehAdvancedExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/BokehAdvancedExtenderImpl.java
index c1df4f4..4093211 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/BokehAdvancedExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/BokehAdvancedExtenderImpl.java
@@ -16,13 +16,15 @@
 
 package androidx.camera.extensions.impl.advanced;
 
-import android.annotation.SuppressLint;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.util.Range;
 import android.util.Size;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import java.util.List;
 import java.util.Map;
 
@@ -33,58 +35,82 @@
  *
  * @since 1.2
  */
-@SuppressLint("UnknownNullness")
 public class BokehAdvancedExtenderImpl implements AdvancedExtenderImpl {
     public BokehAdvancedExtenderImpl() {
     }
 
     @Override
-    public boolean isExtensionAvailable(String cameraId,
-            Map<String, CameraCharacteristics> characteristicsMap) {
-        return false;
+    public boolean isExtensionAvailable(@NonNull String cameraId,
+            @NonNull Map<String, CameraCharacteristics> characteristicsMap) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
-    public void init(String cameraId,
-            Map<String, CameraCharacteristics> characteristicsMap) {
+    public void init(@NonNull String cameraId,
+            @NonNull Map<String, CameraCharacteristics> characteristicsMap) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @Nullable
     public Range<Long> getEstimatedCaptureLatencyRange(
-            String cameraId, Size size, int imageFormat) {
-        return null;
+            @NonNull String cameraId, @Nullable Size size, int imageFormat) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @NonNull
     public Map<Integer, List<Size>> getSupportedPreviewOutputResolutions(
-            String cameraId) {
-        return null;
+            @NonNull String cameraId) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @NonNull
     public Map<Integer, List<Size>> getSupportedCaptureOutputResolutions(
-            String cameraId) {
-        return null;
+            @NonNull String cameraId) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
-    public List<Size> getSupportedYuvAnalysisResolutions(
-            String cameraId) {
-        return null;
+    @NonNull
+    public Map<Integer, List<Size>> getSupportedPostviewResolutions(
+            @NonNull Size captureSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @Nullable
+    public List<Size> getSupportedYuvAnalysisResolutions(@NonNull String cameraId) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    @NonNull
     public SessionProcessorImpl createSessionProcessor() {
-        return null;
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @NonNull
     public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
-        return null;
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @NonNull
     public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
-        return null;
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    @NonNull
+    public boolean isCaptureProcessProgressAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 }
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/Camera2OutputConfigImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/Camera2OutputConfigImpl.java
index 68de01b..e20ac87 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/Camera2OutputConfigImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/Camera2OutputConfigImpl.java
@@ -16,15 +16,16 @@
 
 package androidx.camera.extensions.impl.advanced;
 
-import android.annotation.SuppressLint;
+import androidx.annotation.Nullable;
 
 import java.util.List;
 
 /**
  * A config representing a {@link android.hardware.camera2.params.OutputConfiguration} where
  * Surface will be created by the information in this config.
+ *
+ * @since 1.2
  */
-@SuppressLint("UnknownNullness")
 public interface Camera2OutputConfigImpl {
     /**
      * Gets thd id of this output config. The id can be used to identify the stream in vendor
@@ -41,11 +42,13 @@
     /**
      * Gets the physical camera id. Returns null if not specified.
      */
+    @Nullable
     String getPhysicalCameraId();
 
     /**
      * If non-null, enable surface sharing and add the surface constructed by the return
      * Camera2OutputConfig.
      */
+    @Nullable
     List<Camera2OutputConfigImpl> getSurfaceSharingOutputConfigs();
 }
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/Camera2OutputConfigImplBuilder.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/Camera2OutputConfigImplBuilder.java
index a66d3ce..4de3ede 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/Camera2OutputConfigImplBuilder.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/Camera2OutputConfigImplBuilder.java
@@ -16,11 +16,13 @@
 
 package androidx.camera.extensions.impl.advanced;
 
-import android.annotation.SuppressLint;
 import android.hardware.camera2.params.OutputConfiguration;
 import android.util.Size;
 import android.view.Surface;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -28,15 +30,15 @@
 /**
  * A builder implementation to help OEM build the {@link Camera2OutputConfigImpl} instance.
  */
-@SuppressLint("UnknownNullness")
 public class Camera2OutputConfigImplBuilder {
     static AtomicInteger sLastId = new AtomicInteger(0);
     private OutputConfigImplImpl mOutputConfig;
     private int mSurfaceGroupId = OutputConfiguration.SURFACE_GROUP_ID_NONE;
+    private int mOutputConfigId = -1;
     private String mPhysicalCameraId;
     private List<Camera2OutputConfigImpl> mSurfaceSharingConfigs;
 
-    private Camera2OutputConfigImplBuilder(OutputConfigImplImpl outputConfig) {
+    private Camera2OutputConfigImplBuilder(@NonNull OutputConfigImplImpl outputConfig) {
         mOutputConfig = outputConfig;
     }
 
@@ -48,8 +50,9 @@
      * Creates a {@link Camera2OutputConfigImpl} that represents a {@link android.media.ImageReader}
      * with the given parameters.
      */
+    @NonNull
     public static Camera2OutputConfigImplBuilder newImageReaderConfig(
-            Size size, int imageFormat, int maxImages) {
+            @NonNull Size size, int imageFormat, int maxImages) {
         return new Camera2OutputConfigImplBuilder(
                 new ImageReaderOutputConfigImplImpl(size, imageFormat, maxImages));
     }
@@ -58,6 +61,7 @@
      * Creates a {@link Camera2OutputConfigImpl} that represents a MultiResolutionImageReader with
      * the given parameters.
      */
+    @NonNull
     public static Camera2OutputConfigImplBuilder newMultiResolutionImageReaderConfig(
             int imageFormat, int maxImages) {
         return new Camera2OutputConfigImplBuilder(
@@ -67,15 +71,17 @@
     /**
      * Creates a {@link Camera2OutputConfigImpl} that contains the Surface directly.
      */
-    public static Camera2OutputConfigImplBuilder newSurfaceConfig(Surface surface) {
+    @NonNull
+    public static Camera2OutputConfigImplBuilder newSurfaceConfig(@NonNull Surface surface) {
         return new Camera2OutputConfigImplBuilder(new SurfaceOutputConfigImplImpl(surface));
     }
 
     /**
      * Adds a {@link Camera2SessionConfigImpl} to be shared with current config.
      */
+    @NonNull
     public Camera2OutputConfigImplBuilder addSurfaceSharingOutputConfig(
-            Camera2OutputConfigImpl camera2OutputConfig) {
+            @NonNull Camera2OutputConfigImpl camera2OutputConfig) {
         if (mSurfaceSharingConfigs == null) {
             mSurfaceSharingConfigs = new ArrayList<>();
         }
@@ -87,7 +93,8 @@
     /**
      * Sets a physical camera id.
      */
-    public Camera2OutputConfigImplBuilder setPhysicalCameraId(String physicalCameraId) {
+    @NonNull
+    public Camera2OutputConfigImplBuilder setPhysicalCameraId(@Nullable String physicalCameraId) {
         mPhysicalCameraId = physicalCameraId;
         return this;
     }
@@ -95,16 +102,32 @@
     /**
      * Sets surface group id.
      */
+    @NonNull
     public Camera2OutputConfigImplBuilder setSurfaceGroupId(int surfaceGroupId) {
         mSurfaceGroupId = surfaceGroupId;
         return this;
     }
 
     /**
+     * Sets Output Config id (Optional: Atomic Integer will be used if this function is not called)
+     */
+    @NonNull
+    public Camera2OutputConfigImplBuilder setOutputConfigId(int outputConfigId) {
+        mOutputConfigId = outputConfigId;
+        return this;
+    }
+
+    /**
      * Build a {@link Camera2OutputConfigImpl} instance.
      */
+    @NonNull
     public Camera2OutputConfigImpl build() {
-        mOutputConfig.setId(getNextId());
+        // Sets an output config id otherwise an output config id will be generated
+        if (mOutputConfigId == -1) {
+            mOutputConfig.setId(getNextId());
+        } else {
+            mOutputConfig.setId(mOutputConfigId);
+        }
         mOutputConfig.setPhysicalCameraId(mPhysicalCameraId);
         mOutputConfig.setSurfaceGroup(mSurfaceGroupId);
         mOutputConfig.setSurfaceSharingConfigs(mSurfaceSharingConfigs);
@@ -135,11 +158,13 @@
         }
 
         @Override
+        @Nullable
         public String getPhysicalCameraId() {
             return mPhysicalCameraId;
         }
 
         @Override
+        @Nullable
         public List<Camera2OutputConfigImpl> getSurfaceSharingOutputConfigs() {
             return mSurfaceSharingConfigs;
         }
@@ -152,25 +177,26 @@
             mSurfaceGroup = surfaceGroup;
         }
 
-        public void setPhysicalCameraId(String physicalCameraId) {
+        public void setPhysicalCameraId(@Nullable String physicalCameraId) {
             mPhysicalCameraId = physicalCameraId;
         }
 
         public void setSurfaceSharingConfigs(
-                List<Camera2OutputConfigImpl> surfaceSharingConfigs) {
+                @Nullable List<Camera2OutputConfigImpl> surfaceSharingConfigs) {
             mSurfaceSharingConfigs = surfaceSharingConfigs;
         }
     }
 
     private static class SurfaceOutputConfigImplImpl extends OutputConfigImplImpl
             implements SurfaceOutputConfigImpl {
-        private Surface mSurface;
+        private final Surface mSurface;
 
-        SurfaceOutputConfigImplImpl(Surface surface) {
+        SurfaceOutputConfigImplImpl(@NonNull Surface surface) {
             mSurface = surface;
         }
 
         @Override
+        @NonNull
         public Surface getSurface() {
             return mSurface;
         }
@@ -178,17 +204,18 @@
 
     private static class ImageReaderOutputConfigImplImpl extends OutputConfigImplImpl
             implements ImageReaderOutputConfigImpl {
-        private Size mSize;
-        private int mImageFormat;
-        private int mMaxImages;
+        private final Size mSize;
+        private final int mImageFormat;
+        private final int mMaxImages;
 
-        ImageReaderOutputConfigImplImpl(Size size, int imageFormat, int maxImages) {
+        ImageReaderOutputConfigImplImpl(@NonNull Size size, int imageFormat, int maxImages) {
             mSize = size;
             mImageFormat = imageFormat;
             mMaxImages = maxImages;
         }
 
         @Override
+        @NonNull
         public Size getSize() {
             return mSize;
         }
@@ -206,8 +233,8 @@
 
     private static class MultiResolutionImageReaderOutputConfigImplImpl extends OutputConfigImplImpl
             implements MultiResolutionImageReaderOutputConfigImpl {
-        private int mImageFormat;
-        private int mMaxImages;
+        private final int mImageFormat;
+        private final int mMaxImages;
 
         MultiResolutionImageReaderOutputConfigImplImpl(int imageFormat, int maxImages) {
             mImageFormat = imageFormat;
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImpl.java
index d121717..e0559fb 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImpl.java
@@ -16,26 +16,30 @@
 
 package androidx.camera.extensions.impl.advanced;
 
-import android.annotation.SuppressLint;
 import android.hardware.camera2.CaptureRequest;
 
+import androidx.annotation.NonNull;
+
 import java.util.List;
 import java.util.Map;
 
 /**
  * A config representing a {@link android.hardware.camera2.params.SessionConfiguration}
+ *
+ * @since 1.2
  */
-@SuppressLint("UnknownNullness")
 public interface Camera2SessionConfigImpl {
     /**
      * Returns all the {@link Camera2OutputConfigImpl}s that will be used to create
      * {@link android.hardware.camera2.params.OutputConfiguration}.
      */
+    @NonNull
     List<Camera2OutputConfigImpl> getOutputConfigs();
 
     /**
      * Gets all the parameters to create the session parameters with.
      */
+    @NonNull
     Map<CaptureRequest.Key<?>, Object> getSessionParameters();
 
     /**
@@ -43,4 +47,17 @@
      * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters}.
      */
     int getSessionTemplateId();
-}
+
+
+    /**
+     * Retrieves the session type to be used when initializing the
+     * {@link android.hardware.camera2.CameraCaptureSession}.
+     *
+     * @return Camera capture session type. Regular and vendor specific types are supported but
+     * not high speed values. The extension can return -1 in which case the camera capture session
+     * will be configured to use the default regular type.
+     *
+     * @since 1.4
+     */
+    int getSessionType();
+}
\ No newline at end of file
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImplBuilder.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImplBuilder.java
index a301166..d1bb69a 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImplBuilder.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImplBuilder.java
@@ -16,10 +16,11 @@
 
 package androidx.camera.extensions.impl.advanced;
 
-
-import android.annotation.SuppressLint;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.SessionConfiguration;
+
+import androidx.annotation.NonNull;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -29,9 +30,9 @@
 /**
  * A builder implementation to help OEM build the {@link Camera2SessionConfigImpl} instance.
  */
-@SuppressLint("UnknownNullness")
 public class Camera2SessionConfigImplBuilder {
     private int mSessionTemplateId = CameraDevice.TEMPLATE_PREVIEW;
+    private int mSessionType = SessionConfiguration.SESSION_REGULAR;
     Map<CaptureRequest.Key<?>, Object> mSessionParameters = new HashMap<>();
     List<Camera2OutputConfigImpl> mCamera2OutputConfigs = new ArrayList<>();
 
@@ -41,8 +42,9 @@
     /**
      * Adds a output config.
      */
+    @NonNull
     public Camera2SessionConfigImplBuilder addOutputConfig(
-            Camera2OutputConfigImpl outputConfig) {
+            @NonNull Camera2OutputConfigImpl outputConfig) {
         mCamera2OutputConfigs.add(outputConfig);
         return this;
     }
@@ -50,8 +52,9 @@
     /**
      * Sets session parameters.
      */
+    @NonNull
     public <T> Camera2SessionConfigImplBuilder addSessionParameter(
-            CaptureRequest.Key<T> key, T value) {
+            @NonNull CaptureRequest.Key<T> key, @NonNull T value) {
         mSessionParameters.put(key, value);
         return this;
     }
@@ -59,6 +62,7 @@
     /**
      * Sets the template id for session parameters request.
      */
+    @NonNull
     public Camera2SessionConfigImplBuilder setSessionTemplateId(int templateId) {
         mSessionTemplateId = templateId;
         return this;
@@ -74,6 +78,7 @@
     /**
      * Gets the session parameters.
      */
+    @NonNull
     public Map<CaptureRequest.Key<?>, Object> getSessionParameters() {
         return mSessionParameters;
     }
@@ -81,35 +86,48 @@
     /**
      * Gets all the output configs.
      */
+    @NonNull
     public List<Camera2OutputConfigImpl> getCamera2OutputConfigs() {
         return mCamera2OutputConfigs;
     }
 
     /**
+     * Gets the camera capture session type.
+     */
+    public int getSessionType() {
+        return mSessionType;
+    }
+
+    /**
      * Builds a {@link Camera2SessionConfigImpl} instance.
      */
+    @NonNull
     public Camera2SessionConfigImpl build() {
         return new Camera2SessionConfigImplImpl(this);
     }
 
     private static class Camera2SessionConfigImplImpl implements
             Camera2SessionConfigImpl {
-        int mSessionTemplateId;
-        Map<CaptureRequest.Key<?>, Object> mSessionParameters;
-        List<Camera2OutputConfigImpl> mCamera2OutputConfigs;
+        private final int mSessionTemplateId;
+        private final int mSessionType;
+        private final Map<CaptureRequest.Key<?>, Object> mSessionParameters;
+        private final List<Camera2OutputConfigImpl> mCamera2OutputConfigs;
 
-        Camera2SessionConfigImplImpl(Camera2SessionConfigImplBuilder builder) {
+        Camera2SessionConfigImplImpl(@NonNull Camera2SessionConfigImplBuilder builder) {
             mSessionTemplateId = builder.getSessionTemplateId();
             mSessionParameters = builder.getSessionParameters();
             mCamera2OutputConfigs = builder.getCamera2OutputConfigs();
+            mSessionType = builder.getSessionType();
         }
 
         @Override
+        @NonNull
         public List<Camera2OutputConfigImpl> getOutputConfigs() {
             return mCamera2OutputConfigs;
         }
 
         @Override
+        @NonNull
         public Map<CaptureRequest.Key<?>, Object> getSessionParameters() {
             return mSessionParameters;
         }
@@ -118,6 +136,11 @@
         public int getSessionTemplateId() {
             return mSessionTemplateId;
         }
+
+        @Override
+        public int getSessionType() {
+            return mSessionType;
+        }
     }
 }
 
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/HdrAdvancedExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/HdrAdvancedExtenderImpl.java
index 1621f3e..3d682ec 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/HdrAdvancedExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/HdrAdvancedExtenderImpl.java
@@ -16,13 +16,15 @@
 
 package androidx.camera.extensions.impl.advanced;
 
-import android.annotation.SuppressLint;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.util.Range;
 import android.util.Size;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import java.util.List;
 import java.util.Map;
 
@@ -33,59 +35,82 @@
  *
  * @since 1.2
  */
-@SuppressLint("UnknownNullness")
 public class HdrAdvancedExtenderImpl implements AdvancedExtenderImpl {
     public HdrAdvancedExtenderImpl() {
     }
 
     @Override
-    public boolean isExtensionAvailable(String cameraId,
-            Map<String, CameraCharacteristics> characteristicsMap) {
-        return false;
+    public boolean isExtensionAvailable(@NonNull String cameraId,
+            @NonNull Map<String, CameraCharacteristics> characteristicsMap) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
-    public void init(String cameraId,
-            Map<String, CameraCharacteristics> characteristicsMap) {
+    public void init(@NonNull String cameraId,
+            @NonNull Map<String, CameraCharacteristics> characteristicsMap) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @Nullable
     public Range<Long> getEstimatedCaptureLatencyRange(
-            String cameraId, Size size, int imageFormat) {
-        return null;
+            @NonNull String cameraId, @Nullable Size size, int imageFormat) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @NonNull
     public Map<Integer, List<Size>> getSupportedPreviewOutputResolutions(
-            String cameraId) {
-        return null;
+            @NonNull String cameraId) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
 
     @Override
+    @NonNull
     public Map<Integer, List<Size>> getSupportedCaptureOutputResolutions(
-            String cameraId) {
-        return null;
+            @NonNull String cameraId) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
-    public List<Size> getSupportedYuvAnalysisResolutions(
-            String cameraId) {
-        return null;
+    @NonNull
+    public Map<Integer, List<Size>> getSupportedPostviewResolutions(
+            @NonNull Size captureSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @Nullable
+    public List<Size> getSupportedYuvAnalysisResolutions(@NonNull String cameraId) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    @NonNull
     public SessionProcessorImpl createSessionProcessor() {
-        return null;
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @NonNull
     public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
-        return null;
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @NonNull
     public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
-        return null;
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 }
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/ImageProcessorImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/ImageProcessorImpl.java
index ce17c4f..841c6ac 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/ImageProcessorImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/ImageProcessorImpl.java
@@ -22,12 +22,13 @@
  * A interface to receive and process the upcoming next available Image.
  *
  * <p>Implemented by OEM.
+ *
+ * @since 1.2
  */
 @SuppressLint("UnknownNullness")
 public interface ImageProcessorImpl {
     /**
-     * The reference count will be decremented when this method returns. If an extension wants
-     * to hold onto the image it should increment the reference count in this method and
+     * The reference count will not be decremented when this method returns. Extensions must
      * decrement it when the image is no longer needed.
      *
      * <p>If OEM is not closing(decrement) the image fast enough, the imageReference passed
@@ -50,5 +51,5 @@
             long timestampNs,
             ImageReferenceImpl imageReference,
             String physicalCameraId
-            );
+    );
 }
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/ImageReaderOutputConfigImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/ImageReaderOutputConfigImpl.java
index ca4dcaf..2144588 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/ImageReaderOutputConfigImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/ImageReaderOutputConfigImpl.java
@@ -16,17 +16,20 @@
 
 package androidx.camera.extensions.impl.advanced;
 
-import android.annotation.SuppressLint;
 import android.util.Size;
 
+import androidx.annotation.NonNull;
+
 /**
  * Surface will be created by constructing a ImageReader.
+ *
+ * @since 1.2
  */
-@SuppressLint("UnknownNullness")
 public interface ImageReaderOutputConfigImpl extends Camera2OutputConfigImpl {
     /**
      * Returns the size of the surface.
      */
+    @NonNull
     Size getSize();
 
     /**
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/ImageReferenceImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/ImageReferenceImpl.java
index 95f2c3b..647fbf9 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/ImageReferenceImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/ImageReferenceImpl.java
@@ -16,17 +16,19 @@
 
 package androidx.camera.extensions.impl.advanced;
 
-import android.annotation.SuppressLint;
 import android.media.Image;
 
+import androidx.annotation.Nullable;
+
 /**
  * A Image reference container that enables the Image sharing between Camera2/CameraX and OEM
  * using reference counting. The wrapped Image will be closed once the reference count
  * reaches 0.
  *
  * <p>Implemented by Camera2/CameraX.
+ *
+ * @since 1.2
  */
-@SuppressLint("UnknownNullness")
 public interface ImageReferenceImpl {
 
     /**
@@ -46,5 +48,6 @@
      * Return the Android image. This object MUST not be closed directly.
      * Returns null when the reference count is zero.
      */
+    @Nullable
     Image get();
 }
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/MultiResolutionImageReaderOutputConfigImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/MultiResolutionImageReaderOutputConfigImpl.java
index c3ad61b..ccc229d 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/MultiResolutionImageReaderOutputConfigImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/MultiResolutionImageReaderOutputConfigImpl.java
@@ -18,6 +18,8 @@
 
 /**
  * Surface will be created by constructing a MultiResolutionImageReader.
+ *
+ * @since 1.2
  */
 public interface MultiResolutionImageReaderOutputConfigImpl extends Camera2OutputConfigImpl {
     /**
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/NightAdvancedExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/NightAdvancedExtenderImpl.java
index a5e0775..5c32de8 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/NightAdvancedExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/NightAdvancedExtenderImpl.java
@@ -16,13 +16,15 @@
 
 package androidx.camera.extensions.impl.advanced;
 
-import android.annotation.SuppressLint;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.util.Range;
 import android.util.Size;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import java.util.List;
 import java.util.Map;
 
@@ -33,58 +35,82 @@
  *
  * @since 1.2
  */
-@SuppressLint("UnknownNullness")
 public class NightAdvancedExtenderImpl implements AdvancedExtenderImpl {
     public NightAdvancedExtenderImpl() {
     }
 
     @Override
-    public boolean isExtensionAvailable(String cameraId,
-            Map<String, CameraCharacteristics> characteristicsMap) {
-        return false;
+    public boolean isExtensionAvailable(@NonNull String cameraId,
+            @NonNull Map<String, CameraCharacteristics> characteristicsMap) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
-    public void init(String cameraId,
-            Map<String, CameraCharacteristics> characteristicsMap) {
+    public void init(@NonNull String cameraId,
+            @NonNull Map<String, CameraCharacteristics> characteristicsMap) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @Nullable
     public Range<Long> getEstimatedCaptureLatencyRange(
-            String cameraId, Size size, int imageFormat) {
-        return null;
+            @NonNull String cameraId, @Nullable Size size, int imageFormat) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @NonNull
     public Map<Integer, List<Size>> getSupportedPreviewOutputResolutions(
-            String cameraId) {
-        return null;
+            @NonNull String cameraId) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @NonNull
     public Map<Integer, List<Size>> getSupportedCaptureOutputResolutions(
-            String cameraId) {
-        return null;
+            @NonNull String cameraId) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @NonNull
+    public Map<Integer, List<Size>> getSupportedPostviewResolutions(
+            @NonNull Size captureSize) {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    @Nullable
     public List<Size> getSupportedYuvAnalysisResolutions(
-            String cameraId) {
-        return null;
+            @NonNull String cameraId) {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @NonNull
     public SessionProcessorImpl createSessionProcessor() {
-        return null;
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @NonNull
     public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
-        return null;
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 
     @Override
+    @NonNull
     public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
-        return null;
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isCaptureProcessProgressAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
+    }
+
+    @Override
+    public boolean isPostviewAvailable() {
+        throw new RuntimeException("Stub, replace with implementation.");
     }
 }
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/OutputSurfaceConfigurationImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/OutputSurfaceConfigurationImpl.java
new file mode 100644
index 0000000..1e427b1
--- /dev/null
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/OutputSurfaceConfigurationImpl.java
@@ -0,0 +1,54 @@
+/*
+ * 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.camera.extensions.impl.advanced;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * For specifying the output surface configurations for the extension.
+ *
+ * @since 1.4
+ */
+public interface OutputSurfaceConfigurationImpl {
+    /**
+     * gets the preview {@link OutputSurfaceImpl}, which may contain a <code>null</code> surface
+     * if the app doesn't specify the preview output surface.
+     */
+    @NonNull
+    OutputSurfaceImpl getPreviewOutputSurface();
+
+    /**
+     * gets the still capture {@link OutputSurfaceImpl} which may contain a <code>null</code>
+     * surface if the app doesn't specify the still capture output surface.
+     */
+    @NonNull
+    OutputSurfaceImpl getImageCaptureOutputSurface();
+
+    /**
+     * gets the image analysis {@link OutputSurfaceImpl}.
+     */
+    @Nullable
+    OutputSurfaceImpl getImageAnalysisOutputSurface();
+
+    /**
+     * gets the postview {@link OutputSurfaceImpl} which may contain a <code>null</code> surface
+     * if the app doesn't specify the postview output surface.
+     */
+    @Nullable
+    OutputSurfaceImpl getPostviewOutputSurface();
+}
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/OutputSurfaceImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/OutputSurfaceImpl.java
index f692029..40b5446 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/OutputSurfaceImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/OutputSurfaceImpl.java
@@ -16,23 +16,29 @@
 
 package androidx.camera.extensions.impl.advanced;
 
-import android.annotation.SuppressLint;
 import android.util.Size;
 import android.view.Surface;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 /**
  * For specifying output surface of the extension.
+ *
+ * @since 1.2
  */
-@SuppressLint("UnknownNullness")
 public interface OutputSurfaceImpl {
     /**
-     * Gets the surface.
+     * Gets the surface. It returns null if output surface is not specified.
      */
+    @Nullable
     Surface getSurface();
 
+
     /**
      * Gets the size.
      */
+    @NonNull
     Size getSize();
 
     /**
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/RequestProcessorImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/RequestProcessorImpl.java
index 5185333..5fc8a6f 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/RequestProcessorImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/RequestProcessorImpl.java
@@ -16,45 +16,47 @@
 
 package androidx.camera.extensions.impl.advanced;
 
-import android.annotation.SuppressLint;
 import android.hardware.camera2.CaptureFailure;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.TotalCaptureResult;
 
+import androidx.annotation.NonNull;
+
 import java.util.List;
 import java.util.Map;
 
 /**
  * An Interface to execute Camera2 capture requests.
+ *
+ * @since 1.2
  */
-@SuppressLint("UnknownNullness")
 public interface RequestProcessorImpl {
     /**
      * Sets a {@link ImageProcessorImpl} to receive {@link ImageReferenceImpl} to process.
      */
-    void setImageProcessor(int outputconfigId, ImageProcessorImpl imageProcessor);
+    void setImageProcessor(int outputconfigId, @NonNull ImageProcessorImpl imageProcessor);
 
     /**
      * Submits a request.
      * @return the id of the capture sequence or -1 in case the processor encounters a fatal error
      *         or receives an invalid argument.
      */
-    int submit(Request request, Callback callback);
+    int submit(@NonNull Request request, @NonNull Callback callback);
 
     /**
      * Submits a list of requests.
      * @return the id of the capture sequence or -1 in case the processor encounters a fatal error
      *         or receives an invalid argument.
      */
-    int submit(List<Request> requests, Callback callback);
+    int submit(@NonNull List<Request> requests, @NonNull Callback callback);
 
     /**
      * Set repeating requests.
      * @return the id of the capture sequence or -1 in case the processor encounters a fatal error
      *         or receives an invalid argument.
      */
-    int setRepeating(Request request, Callback callback);
+    int setRepeating(@NonNull Request request, @NonNull Callback callback);
 
 
     /**
@@ -76,16 +78,19 @@
          * Gets the target ids of {@link Camera2OutputConfigImpl} which identifies corresponding
          * Surface to be the targeted for the request.
          */
+        @NonNull
         List<Integer> getTargetOutputConfigIds();
 
         /**
          * Gets all the parameters.
          */
+        @NonNull
         Map<CaptureRequest.Key<?>, Object> getParameters();
 
         /**
          * Gets the template id.
          */
+        @NonNull
         Integer getTemplateId();
     }
 
@@ -94,24 +99,24 @@
      */
     interface Callback {
         void onCaptureStarted(
-                Request request,
+                @NonNull Request request,
                 long frameNumber,
                 long timestamp);
 
         void onCaptureProgressed(
-                Request request,
-                CaptureResult partialResult);
+                @NonNull Request request,
+                @NonNull CaptureResult partialResult);
 
         void onCaptureCompleted(
-                Request request,
-                TotalCaptureResult totalCaptureResult);
+                @NonNull Request request,
+                @NonNull TotalCaptureResult totalCaptureResult);
 
         void onCaptureFailed(
-                Request request,
-                CaptureFailure captureFailure);
+                @NonNull Request request,
+                @NonNull CaptureFailure captureFailure);
 
         void onCaptureBufferLost(
-                Request request,
+                @NonNull Request request,
                 long frameNumber,
                 int outputStreamId);
 
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/SessionProcessorImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/SessionProcessorImpl.java
index e3ace72..1915015 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/SessionProcessorImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/SessionProcessorImpl.java
@@ -16,7 +16,6 @@
 
 package androidx.camera.extensions.impl.advanced;
 
-import android.annotation.SuppressLint;
 import android.content.Context;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
@@ -24,6 +23,7 @@
 import android.util.Pair;
 import android.view.Surface;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import java.util.Map;
@@ -59,17 +59,69 @@
  *
  * (6) {@link #deInitSession}: called when CameraCaptureSession is closed.
  * </pre>
+ *
+ * @since 1.2
  */
-@SuppressLint("UnknownNullness")
 public interface SessionProcessorImpl {
     /**
      * Initializes the session for the extension. This is where the OEMs allocate resources for
      * preparing a CameraCaptureSession. After initSession() is called, the camera ID,
      * cameraCharacteristics and context will not change until deInitSession() has been called.
      *
-     * <p>CameraX specifies the output surface configurations for preview, image capture and image
-     * analysis[optional]. And OEM returns a {@link Camera2SessionConfigImpl} which consists of a
-     * list of {@link Camera2OutputConfigImpl} and session parameters. The
+     * <p>CameraX / Camera2 specifies the output surface configurations for preview using
+     * {@link OutputSurfaceConfigurationImpl#getPreviewOutputSurface}, image capture using
+     * {@link OutputSurfaceConfigurationImpl#getImageCaptureOutputSurface}, and image analysis
+     * [optional] using {@link OutputSurfaceConfigurationImpl#getImageAnalysisOutputSurface}.
+     * And OEM returns a {@link Camera2SessionConfigImpl} which consists of a list of
+     * {@link Camera2OutputConfigImpl} and session parameters. The {@link Camera2SessionConfigImpl}
+     * will be used to configure the CameraCaptureSession.
+     *
+     * <p>OEM is responsible for outputting correct camera images output to these output surfaces.
+     * OEM can have the following options to enable the output:
+     * <pre>
+     * (1) Add these output surfaces in CameraCaptureSession directly using
+     * {@link Camera2OutputConfigImplBuilder#newSurfaceConfig(Surface)} }. Processing is done in
+     * HAL.
+     *
+     * (2) Use surface sharing with other surface by calling
+     * {@link Camera2OutputConfigImplBuilder#addSurfaceSharingOutputConfig(Camera2OutputConfigImpl)}
+     * to add the output surface to the other {@link Camera2OutputConfigImpl}.
+     *
+     * (3) Process output from other surfaces (RAW, YUV..) and write the result to the output
+     * surface. The output surface won't be contained in the returned
+     * {@link Camera2SessionConfigImpl}.
+     * </pre>
+     *
+     * <p>{@link Camera2OutputConfigImplBuilder} and {@link Camera2SessionConfigImplBuilder}
+     * implementations are provided in the stub for OEM to construct the
+     * {@link Camera2OutputConfigImpl} and {@link Camera2SessionConfigImpl} instances.
+     *
+     * @param surfaceConfigs contains output surfaces for preview, image capture, and an
+     *                       optional output config for image analysis (YUV_420_888).
+     * @return a {@link Camera2SessionConfigImpl} consisting of a list of
+     * {@link Camera2OutputConfigImpl} and session parameters which will decide the
+     * {@link android.hardware.camera2.params.SessionConfiguration} for configuring the
+     * CameraCaptureSession. Please note that the OutputConfiguration list may not be part of any
+     * supported or mandatory stream combination BUT OEM must ensure this list will always
+     * produce a valid camera capture session.
+     *
+     * @since 1.4
+     */
+    @NonNull
+    Camera2SessionConfigImpl initSession(
+            @NonNull String cameraId,
+            @NonNull Map<String, CameraCharacteristics> cameraCharacteristicsMap,
+            @NonNull Context context,
+            @NonNull OutputSurfaceConfigurationImpl surfaceConfigs);
+
+    /**
+     * Initializes the session for the extension. This is where the OEMs allocate resources for
+     * preparing a CameraCaptureSession. After initSession() is called, the camera ID,
+     * cameraCharacteristics and context will not change until deInitSession() has been called.
+     *
+     * <p>CameraX / Camera 2 specifies the output surface configurations for preview, image capture
+     * and image analysis[optional]. And OEM returns a {@link Camera2SessionConfigImpl} which
+     * consists of a list of {@link Camera2OutputConfigImpl} and session parameters. The
      * {@link Camera2SessionConfigImpl} will be used to configure the CameraCaptureSession.
      *
      * <p>OEM is responsible for outputting correct camera images output to these output surfaces.
@@ -92,8 +144,12 @@
      * implementations are provided in the stub for OEM to construct the
      * {@link Camera2OutputConfigImpl} and {@link Camera2SessionConfigImpl} instances.
      *
-     * @param previewSurfaceConfig       output surface for preview
-     * @param imageCaptureSurfaceConfig  output surface for image capture.
+     * @param previewSurfaceConfig       output surface for preview, which may contain a
+     *                                   <code>null</code> surface if the app doesn't specify the
+     *                                   preview surface.
+     * @param imageCaptureSurfaceConfig  output surface for still capture, which may contain a
+     *                                   <code>null</code> surface if the app doesn't specify the
+     *                                   still capture surface.
      * @param imageAnalysisSurfaceConfig an optional output config for image analysis
      *                                   (YUV_420_888).
      * @return a {@link Camera2SessionConfigImpl} consisting of a list of
@@ -103,13 +159,14 @@
      * supported or mandatory stream combination BUT OEM must ensure this list will always
      * produce a valid camera capture session.
      */
+    @NonNull
     Camera2SessionConfigImpl initSession(
-            String cameraId,
-            Map<String, CameraCharacteristics> cameraCharacteristicsMap,
-            Context context,
-            OutputSurfaceImpl previewSurfaceConfig,
-            OutputSurfaceImpl imageCaptureSurfaceConfig,
-            OutputSurfaceImpl imageAnalysisSurfaceConfig);
+            @NonNull String cameraId,
+            @NonNull Map<String, CameraCharacteristics> cameraCharacteristicsMap,
+            @NonNull Context context,
+            @NonNull OutputSurfaceImpl previewSurfaceConfig,
+            @NonNull OutputSurfaceImpl imageCaptureSurfaceConfig,
+            @Nullable OutputSurfaceImpl imageAnalysisSurfaceConfig);
 
     /**
      * Notify to de-initialize the extension. This callback will be invoked after
@@ -124,7 +181,7 @@
      * expected that the OEM would (eventually) update the repeating request if the keys are
      * supported. Setting a value to null explicitly un-sets the value.
      */
-    void setParameters(Map<CaptureRequest.Key<?>, Object> parameters);
+    void setParameters(@NonNull Map<CaptureRequest.Key<?>, Object> parameters);
 
     /**
      * CameraX / Camera2 will call this interface in response to client requests involving
@@ -140,7 +197,8 @@
      *
      * @since 1.3
      */
-    int startTrigger(Map<CaptureRequest.Key<?>, Object> triggers, CaptureCallback callback);
+    int startTrigger(@NonNull Map<CaptureRequest.Key<?>, Object> triggers,
+            @NonNull CaptureCallback callback);
 
     /**
      * This will be invoked once after the {@link android.hardware.camera2.CameraCaptureSession}
@@ -148,7 +206,7 @@
      * requests or set repeating requests. This ExtensionRequestProcessor will be valid to use
      * until onCaptureSessionEnd is called.
      */
-    void onCaptureSessionStart(RequestProcessorImpl requestProcessor);
+    void onCaptureSessionStart(@NonNull RequestProcessorImpl requestProcessor);
 
     /**
      * This will be invoked before the {@link android.hardware.camera2.CameraCaptureSession} is
@@ -165,7 +223,7 @@
      * @param callback a callback to report the status.
      * @return the id of the capture sequence.
      */
-    int startRepeating(CaptureCallback callback);
+    int startRepeating(@NonNull CaptureCallback callback);
 
     /**
      * Stop the repeating request. To prevent OEM from not calling stopRepeating, CameraX will
@@ -188,7 +246,27 @@
      * @param callback a callback to report the status.
      * @return the id of the capture sequence.
      */
-    int startCapture(CaptureCallback callback);
+    int startCapture(@NonNull CaptureCallback callback);
+
+    /**
+     * Start a multi-frame capture with a postview. {@link #startCapture(CaptureCallback)}
+     * will be used for captures without a postview request.
+     *
+     * Postview will be available before the capture. Upon postview completion,
+     * {@code OnImageAvailableListener#onImageAvailable} will be called on the ImageReader
+     * that creates the postview output surface. When the capture is completed,
+     * {@link CaptureCallback#onCaptureSequenceCompleted} is called and
+     * {@code OnImageAvailableListener#onImageAvailable} will also be called on the ImageReader
+     * that creates the image capture output surface.
+     *
+     * <p>Only one capture can perform at a time. Starting a capture when another capture is
+     * running will cause onCaptureFailed to be called immediately.
+     *
+     * @param callback a callback to report the status.
+     * @return the id of the capture sequence.
+     * @since 1.4
+     */
+    int startCaptureWithPostview(@NonNull CaptureCallback callback);
 
     /**
      * Abort all capture tasks.
@@ -217,9 +295,7 @@
      * @since 1.4
      */
     @Nullable
-    default Pair<Long, Long> getRealtimeCaptureLatency() {
-        return null;
-    };
+    Pair<Long, Long> getRealtimeCaptureLatency();
 
     /**
      * Callback for notifying the status of {@link #startCapture(CaptureCallback)} and
@@ -303,8 +379,24 @@
          *                             as part of this callback. Both Camera2 and CameraX guarantee
          *                             that those two settings and results are always supported and
          *                             applied by the corresponding framework.
+         * @since 1.3
          */
-        void onCaptureCompleted(long timestamp, int captureSequenceId,
-                Map<CaptureResult.Key, Object> result);
+        default void onCaptureCompleted(long timestamp, int captureSequenceId,
+                @NonNull Map<CaptureResult.Key, Object> result) {}
+
+        /**
+         * Capture progress callback that needs to be called when the process capture is
+         * ongoing and includes the estimated progress of the processing.
+         *
+         * <p>Extensions must ensure that they always call this callback with monotonically
+         * increasing values.</p>
+         *
+         * <p>Extensions are allowed to trigger this callback multiple times but at the minimum the
+         * callback is expected to be called once when processing is done with value 100.</p>
+         *
+         * @param progress             Value between 0 and 100.
+         * @since 1.4
+         */
+        default void onCaptureProcessProgressed(int progress) {}
     }
 }
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/SurfaceOutputConfigImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/SurfaceOutputConfigImpl.java
index 7b8d83c..72e8816 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/SurfaceOutputConfigImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/advanced/SurfaceOutputConfigImpl.java
@@ -16,16 +16,19 @@
 
 package androidx.camera.extensions.impl.advanced;
 
-import android.annotation.SuppressLint;
 import android.view.Surface;
 
+import androidx.annotation.NonNull;
+
 /**
  * Use Surface directly to create the OutputConfiguration.
+ *
+ * @since 1.2
  */
-@SuppressLint("UnknownNullness")
 public interface SurfaceOutputConfigImpl extends Camera2OutputConfigImpl {
     /**
      * Get the {@link Surface}. It'll return valid surface only when type is TYPE_SURFACE.
      */
+    @NonNull
     Surface getSurface();
 }
diff --git a/camera/integration-tests/coretestapp/lint-baseline.xml b/camera/integration-tests/coretestapp/lint-baseline.xml
index 9e9f16f..e580691 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-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.2.0-beta01)" variant="all" version="8.2.0-beta01">
+<issues format="6" by="lint 8.3.0-alpha02" type="baseline" client="cli" dependencies="false" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
 
     <issue
         id="BanThreadSleep"
@@ -13,35 +13,8 @@
     <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/CameraXActivity.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/CameraXActivity.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/CameraXActivity.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="                                                                               ~~~~~~~~~">
+        errorLine1="                    if (canDeviceWriteToMediaStore()) {"
+        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/camera/integration/core/CameraXActivity.java"/>
     </issue>
@@ -75,6 +48,33 @@
 
     <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/CameraXActivity.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/CameraXActivity.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/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="                        videoFilePath = getAbsolutePathFromUri("
         errorLine2="                                        ~~~~~~~~~~~~~~~~~~~~~~">
@@ -256,35 +256,8 @@
     <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="                                                                       ~~~~~~~~~">
+        errorLine1="            if (canDeviceWriteToMediaStore()) {"
+        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
     </issue>
@@ -318,6 +291,33 @@
 
     <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.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="                               ~~~~~~~~~~~~~~~~~~~~~~">
diff --git a/car/app/app-samples/navigation/mobile/lint-baseline.xml b/car/app/app-samples/navigation/mobile/lint-baseline.xml
deleted file mode 100644
index d28831e..0000000
--- a/car/app/app-samples/navigation/mobile/lint-baseline.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?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">
-
-    <issue
-        id="ForegroundServicePermission"
-        message="foregroundServiceType:location requires permission:[android.permission.FOREGROUND_SERVICE_LOCATION] AND any permission in list:[android.permission.ACCESS_COARSE_LOCATION, android.permission.ACCESS_FINE_LOCATION]"
-        errorLine1="    &lt;service"
-        errorLine2="    ^">
-        <location
-            file="src/main/AndroidManifest.xml"/>
-    </issue>
-
-</issues>
diff --git a/car/app/app-samples/showcase/automotive/lint-baseline.xml b/car/app/app-samples/showcase/automotive/lint-baseline.xml
deleted file mode 100644
index a726fae..0000000
--- a/car/app/app-samples/showcase/automotive/lint-baseline.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?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/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/navigationtemplates/RoutingDemoModels.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/navigationtemplates/RoutingDemoModels.java
index b51619b..bdc3771 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/navigationtemplates/RoutingDemoModels.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/navigationtemplates/RoutingDemoModels.java
@@ -24,6 +24,7 @@
 import android.text.SpannableString;
 import android.text.Spanned;
 
+import androidx.annotation.ColorInt;
 import androidx.annotation.NonNull;
 import androidx.car.app.AppManager;
 import androidx.car.app.CarContext;
@@ -189,10 +190,14 @@
             @NonNull CarContext carContext, @NonNull OnClickListener onStopNavigation) {
         ActionStrip.Builder builder = new ActionStrip.Builder();
         if (carContext.getCarAppApiLevel() >= CarAppApiLevels.LEVEL_5) {
+            @ColorInt int actionButtonRed = 0xffb40404;
             builder.addAction(
                     new Action.Builder()
+                            .setFlags(FLAG_PRIMARY)
+                            .setBackgroundColor(
+                                    CarColor.createCustom(actionButtonRed, actionButtonRed))
                             .setOnClickListener(
-                                    () ->  carContext.getCarService(AppManager.class)
+                                    () -> carContext.getCarService(AppManager.class)
                                             .showAlert(createAlert(carContext)))
                             .setIcon(new CarIcon.Builder(
                                     IconCompat.createWithResource(
diff --git a/car/app/app/lint-baseline.xml b/car/app/app/lint-baseline.xml
index 3cc8390d..8492f99 100644
--- a/car/app/app/lint-baseline.xml
+++ b/car/app/app/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.3.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
 
     <issue
         id="MissingPermission"
@@ -371,15 +371,6 @@
     </issue>
 
     <issue
-        id="UnspecifiedRegisterReceiverFlag"
-        message="`mBroadcastReceiver` \&#xA;is missing `RECEIVER_EXPORTED` or `RECEIVER_NOT_EXPORTED` flag for unprotected \&#xA;broadcasts registered for androidx.car.app.connection.action.CAR_CONNECTION_UPDATED"
-        errorLine1="            mContext.registerReceiver(mBroadcastReceiver, filter);"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/car/app/connection/CarConnectionTypeLiveData.java"/>
-    </issue>
-
-    <issue
         id="UnsafeOptInUsageError"
         message="This declaration is opt-in and its usage should be marked with `@androidx.car.app.annotations.ExperimentalCarApi` or `@OptIn(markerClass = androidx.car.app.annotations.ExperimentalCarApi.class)`"
         errorLine1="            } else if (rowObj instanceof ConversationItem) {"
diff --git a/collection/collection-benchmark/build.gradle b/collection/collection-benchmark/build.gradle
index 2ec43e2..f255aee 100644
--- a/collection/collection-benchmark/build.gradle
+++ b/collection/collection-benchmark/build.gradle
@@ -62,7 +62,7 @@
             }
         }
 
-        androidUnitTest {
+        androidInstrumentedTest {
             dependsOn(commonTest)
             dependencies {
                 implementation(projectOrArtifact(":benchmark:benchmark-junit4"))
diff --git a/collection/collection-benchmark/src/androidUnitTest/AndroidManifest.xml b/collection/collection-benchmark/src/androidInstrumentedTest/AndroidManifest.xml
similarity index 100%
rename from collection/collection-benchmark/src/androidUnitTest/AndroidManifest.xml
rename to collection/collection-benchmark/src/androidInstrumentedTest/AndroidManifest.xml
diff --git a/collection/collection-benchmark/src/androidUnitTest/java/androidx/collection/ArraySetBenchmarkTest.kt b/collection/collection-benchmark/src/androidInstrumentedTest/kotlin/androidx/collection/ArraySetBenchmarkTest.kt
similarity index 100%
rename from collection/collection-benchmark/src/androidUnitTest/java/androidx/collection/ArraySetBenchmarkTest.kt
rename to collection/collection-benchmark/src/androidInstrumentedTest/kotlin/androidx/collection/ArraySetBenchmarkTest.kt
diff --git a/collection/collection-benchmark/src/androidUnitTest/java/androidx/collection/CircularArrayBenchmarkTest.kt b/collection/collection-benchmark/src/androidInstrumentedTest/kotlin/androidx/collection/CircularArrayBenchmarkTest.kt
similarity index 100%
rename from collection/collection-benchmark/src/androidUnitTest/java/androidx/collection/CircularArrayBenchmarkTest.kt
rename to collection/collection-benchmark/src/androidInstrumentedTest/kotlin/androidx/collection/CircularArrayBenchmarkTest.kt
diff --git a/collection/collection-benchmark/src/androidUnitTest/java/androidx/collection/CollectionBenchmarkExt.kt b/collection/collection-benchmark/src/androidInstrumentedTest/kotlin/androidx/collection/CollectionBenchmarkExt.kt
similarity index 100%
rename from collection/collection-benchmark/src/androidUnitTest/java/androidx/collection/CollectionBenchmarkExt.kt
rename to collection/collection-benchmark/src/androidInstrumentedTest/kotlin/androidx/collection/CollectionBenchmarkExt.kt
diff --git a/collection/collection-benchmark/src/androidUnitTest/java/androidx/collection/LruCacheBenchmarkTest.kt b/collection/collection-benchmark/src/androidInstrumentedTest/kotlin/androidx/collection/LruCacheBenchmarkTest.kt
similarity index 100%
rename from collection/collection-benchmark/src/androidUnitTest/java/androidx/collection/LruCacheBenchmarkTest.kt
rename to collection/collection-benchmark/src/androidInstrumentedTest/kotlin/androidx/collection/LruCacheBenchmarkTest.kt
diff --git a/collection/collection-benchmark/src/androidInstrumentedTest/kotlin/androidx/collection/ObjectListBenchmarkTest.kt b/collection/collection-benchmark/src/androidInstrumentedTest/kotlin/androidx/collection/ObjectListBenchmarkTest.kt
new file mode 100644
index 0000000..a6ba28f
--- /dev/null
+++ b/collection/collection-benchmark/src/androidInstrumentedTest/kotlin/androidx/collection/ObjectListBenchmarkTest.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.benchmark.junit4.BenchmarkRule
+import androidx.benchmark.junit4.measureRepeated
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class ObjectListBenchmarkTest {
+    val ObjectCount = 100
+    private val list: ObjectList<String> = MutableObjectList<String>(ObjectCount).also { list ->
+        repeat(ObjectCount) {
+            list += it.toString()
+        }
+    }
+
+    private val array = Array(ObjectCount) { it.toString() }
+
+    @get:Rule
+    val benchmark = BenchmarkRule()
+
+    @Test
+    fun forEach() {
+        benchmark.measureRepeated {
+            @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
+            var last: String
+            list.forEach { element ->
+                last = element
+            }
+        }
+    }
+
+    @Test
+    fun add() {
+        val mutableList = MutableObjectList<String>(ObjectCount)
+        benchmark.measureRepeated {
+            repeat(ObjectCount) {
+                mutableList += array[it]
+            }
+            mutableList.clear()
+        }
+    }
+
+    @Test
+    fun contains() {
+        benchmark.measureRepeated {
+            repeat(ObjectCount) {
+                list.contains(array[it])
+            }
+        }
+    }
+
+    @Test
+    fun get() {
+        benchmark.measureRepeated {
+            repeat(ObjectCount) {
+                list[it]
+            }
+        }
+    }
+
+    @Test
+    fun addAll() {
+        val mutableList = MutableObjectList<String>(ObjectCount)
+        benchmark.measureRepeated {
+            mutableList += list
+            mutableList.clear()
+        }
+    }
+
+    @Test
+    fun removeStart() {
+        val mutableList = MutableObjectList<String>(ObjectCount)
+        benchmark.measureRepeated {
+            mutableList += list
+            repeat(ObjectCount) {
+                mutableList.removeAt(0)
+            }
+        }
+    }
+
+    @Test
+    fun removeEnd() {
+        val mutableList = MutableObjectList<String>(ObjectCount)
+        benchmark.measureRepeated {
+            mutableList += list
+            for (i in ObjectCount - 1 downTo 0) {
+                mutableList.removeAt(i)
+            }
+        }
+    }
+}
diff --git a/collection/collection-benchmark/src/androidUnitTest/java/androidx/collection/ScatterMapBenchmarkTest.kt b/collection/collection-benchmark/src/androidInstrumentedTest/kotlin/androidx/collection/ScatterMapBenchmarkTest.kt
similarity index 100%
rename from collection/collection-benchmark/src/androidUnitTest/java/androidx/collection/ScatterMapBenchmarkTest.kt
rename to collection/collection-benchmark/src/androidInstrumentedTest/kotlin/androidx/collection/ScatterMapBenchmarkTest.kt
diff --git a/collection/collection-benchmark/src/androidUnitTest/java/androidx/collection/ScatterSetBenchmarkTest.kt b/collection/collection-benchmark/src/androidInstrumentedTest/kotlin/androidx/collection/ScatterSetBenchmarkTest.kt
similarity index 100%
rename from collection/collection-benchmark/src/androidUnitTest/java/androidx/collection/ScatterSetBenchmarkTest.kt
rename to collection/collection-benchmark/src/androidInstrumentedTest/kotlin/androidx/collection/ScatterSetBenchmarkTest.kt
diff --git a/collection/collection-benchmark/src/androidUnitTest/java/androidx/collection/SimpleArrayMapBenchmarkTest.kt b/collection/collection-benchmark/src/androidInstrumentedTest/kotlin/androidx/collection/SimpleArrayMapBenchmarkTest.kt
similarity index 100%
rename from collection/collection-benchmark/src/androidUnitTest/java/androidx/collection/SimpleArrayMapBenchmarkTest.kt
rename to collection/collection-benchmark/src/androidInstrumentedTest/kotlin/androidx/collection/SimpleArrayMapBenchmarkTest.kt
diff --git a/collection/collection-benchmark/src/androidUnitTest/java/androidx/collection/SparseArrayFilledBenchmarkTest.kt b/collection/collection-benchmark/src/androidInstrumentedTest/kotlin/androidx/collection/SparseArrayFilledBenchmarkTest.kt
similarity index 100%
rename from collection/collection-benchmark/src/androidUnitTest/java/androidx/collection/SparseArrayFilledBenchmarkTest.kt
rename to collection/collection-benchmark/src/androidInstrumentedTest/kotlin/androidx/collection/SparseArrayFilledBenchmarkTest.kt
diff --git a/collection/collection-benchmark/src/androidUnitTest/java/androidx/collection/junit.kt b/collection/collection-benchmark/src/androidInstrumentedTest/kotlin/androidx/collection/junit.kt
similarity index 100%
rename from collection/collection-benchmark/src/androidUnitTest/java/androidx/collection/junit.kt
rename to collection/collection-benchmark/src/androidInstrumentedTest/kotlin/androidx/collection/junit.kt
diff --git a/collection/collection/api/current.txt b/collection/collection/api/current.txt
index ec619f8..53cd0b5 100644
--- a/collection/collection/api/current.txt
+++ b/collection/collection/api/current.txt
@@ -113,6 +113,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -121,9 +133,27 @@
   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.FloatFloatMap floatFloatMapOf(float key1, float value1);
+    method public static androidx.collection.FloatFloatMap floatFloatMapOf(float key1, float value1, float key2, float value2);
+    method public static androidx.collection.FloatFloatMap floatFloatMapOf(float key1, float value1, float key2, float value2, float key3, float value3);
+    method public static androidx.collection.FloatFloatMap floatFloatMapOf(float key1, float value1, float key2, float value2, float key3, float value3, float key4, float value4);
+    method public static androidx.collection.FloatFloatMap floatFloatMapOf(float key1, float value1, float key2, float value2, float key3, float value3, float key4, float value4, float key5, float value5);
     method public static androidx.collection.MutableFloatFloatMap mutableFloatFloatMapOf();
-    method public static androidx.collection.MutableFloatFloatMap mutableFloatFloatMapOf(kotlin.Pair<java.lang.Float,java.lang.Float>... pairs);
+    method public static androidx.collection.MutableFloatFloatMap mutableFloatFloatMapOf(float key1, float value1);
+    method public static androidx.collection.MutableFloatFloatMap mutableFloatFloatMapOf(float key1, float value1, float key2, float value2);
+    method public static androidx.collection.MutableFloatFloatMap mutableFloatFloatMapOf(float key1, float value1, float key2, float value2, float key3, float value3);
+    method public static androidx.collection.MutableFloatFloatMap mutableFloatFloatMapOf(float key1, float value1, float key2, float value2, float key3, float value3, float key4, float value4);
+    method public static androidx.collection.MutableFloatFloatMap mutableFloatFloatMapOf(float key1, float value1, float key2, float value2, float key3, float value3, float key4, float value4, float key5, float value5);
+  }
+
+  @kotlin.jvm.JvmInline public final value class FloatFloatPair {
+    ctor public FloatFloatPair(float first, float second);
+    method public inline operator float component1();
+    method public inline operator float component2();
+    method public inline float getFirst();
+    method public inline float getSecond();
+    property public final inline float first;
+    property public final inline float second;
   }
 
   public abstract sealed class FloatIntMap {
@@ -145,6 +175,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -153,9 +195,17 @@
   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.FloatIntMap floatIntMapOf(float key1, int value1);
+    method public static androidx.collection.FloatIntMap floatIntMapOf(float key1, int value1, float key2, int value2);
+    method public static androidx.collection.FloatIntMap floatIntMapOf(float key1, int value1, float key2, int value2, float key3, int value3);
+    method public static androidx.collection.FloatIntMap floatIntMapOf(float key1, int value1, float key2, int value2, float key3, int value3, float key4, int value4);
+    method public static androidx.collection.FloatIntMap floatIntMapOf(float key1, int value1, float key2, int value2, float key3, int value3, float key4, int value4, float key5, int value5);
     method public static androidx.collection.MutableFloatIntMap mutableFloatIntMapOf();
-    method public static androidx.collection.MutableFloatIntMap mutableFloatIntMapOf(kotlin.Pair<java.lang.Float,java.lang.Integer>... pairs);
+    method public static androidx.collection.MutableFloatIntMap mutableFloatIntMapOf(float key1, int value1);
+    method public static androidx.collection.MutableFloatIntMap mutableFloatIntMapOf(float key1, int value1, float key2, int value2);
+    method public static androidx.collection.MutableFloatIntMap mutableFloatIntMapOf(float key1, int value1, float key2, int value2, float key3, int value3);
+    method public static androidx.collection.MutableFloatIntMap mutableFloatIntMapOf(float key1, int value1, float key2, int value2, float key3, int value3, float key4, int value4);
+    method public static androidx.collection.MutableFloatIntMap mutableFloatIntMapOf(float key1, int value1, float key2, int value2, float key3, int value3, float key4, int value4, float key5, int value5);
   }
 
   public abstract sealed class FloatList {
@@ -186,6 +236,18 @@
     method public final inline int indexOfLast(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Boolean> predicate);
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function1<? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function1<? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function1<? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function1<? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function1<? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function1<? super java.lang.Float,? extends java.lang.CharSequence> transform);
     method public final float last();
     method public final inline float last(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Boolean> predicate);
     method public final int lastIndexOf(float element);
@@ -229,6 +291,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -237,9 +311,17 @@
   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.FloatLongMap floatLongMapOf(float key1, long value1);
+    method public static androidx.collection.FloatLongMap floatLongMapOf(float key1, long value1, float key2, long value2);
+    method public static androidx.collection.FloatLongMap floatLongMapOf(float key1, long value1, float key2, long value2, float key3, long value3);
+    method public static androidx.collection.FloatLongMap floatLongMapOf(float key1, long value1, float key2, long value2, float key3, long value3, float key4, long value4);
+    method public static androidx.collection.FloatLongMap floatLongMapOf(float key1, long value1, float key2, long value2, float key3, long value3, float key4, long value4, float key5, long value5);
     method public static androidx.collection.MutableFloatLongMap mutableFloatLongMapOf();
-    method public static androidx.collection.MutableFloatLongMap mutableFloatLongMapOf(kotlin.Pair<java.lang.Float,java.lang.Long>... pairs);
+    method public static androidx.collection.MutableFloatLongMap mutableFloatLongMapOf(float key1, long value1);
+    method public static androidx.collection.MutableFloatLongMap mutableFloatLongMapOf(float key1, long value1, float key2, long value2);
+    method public static androidx.collection.MutableFloatLongMap mutableFloatLongMapOf(float key1, long value1, float key2, long value2, float key3, long value3);
+    method public static androidx.collection.MutableFloatLongMap mutableFloatLongMapOf(float key1, long value1, float key2, long value2, float key3, long value3, float key4, long value4);
+    method public static androidx.collection.MutableFloatLongMap mutableFloatLongMapOf(float key1, long value1, float key2, long value2, float key3, long value3, float key4, long value4, float key5, long value5);
   }
 
   public abstract sealed class FloatObjectMap<V> {
@@ -261,6 +343,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -269,9 +363,17 @@
   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.FloatObjectMap<V> floatObjectMapOf(float key1, V value1);
+    method public static <V> androidx.collection.FloatObjectMap<V> floatObjectMapOf(float key1, V value1, float key2, V value2);
+    method public static <V> androidx.collection.FloatObjectMap<V> floatObjectMapOf(float key1, V value1, float key2, V value2, float key3, V value3);
+    method public static <V> androidx.collection.FloatObjectMap<V> floatObjectMapOf(float key1, V value1, float key2, V value2, float key3, V value3, float key4, V value4);
+    method public static <V> androidx.collection.FloatObjectMap<V> floatObjectMapOf(float key1, V value1, float key2, V value2, float key3, V value3, float key4, V value4, float key5, V value5);
     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);
+    method public static <V> androidx.collection.MutableFloatObjectMap<V> mutableFloatObjectMapOf(float key1, V value1);
+    method public static <V> androidx.collection.MutableFloatObjectMap<V> mutableFloatObjectMapOf(float key1, V value1, float key2, V value2);
+    method public static <V> androidx.collection.MutableFloatObjectMap<V> mutableFloatObjectMapOf(float key1, V value1, float key2, V value2, float key3, V value3);
+    method public static <V> androidx.collection.MutableFloatObjectMap<V> mutableFloatObjectMapOf(float key1, V value1, float key2, V value2, float key3, V value3, float key4, V value4);
+    method public static <V> androidx.collection.MutableFloatObjectMap<V> mutableFloatObjectMapOf(float key1, V value1, float key2, V value2, float key3, V value3, float key4, V value4, float key5, V value5);
   }
 
   public abstract sealed class FloatSet {
@@ -288,6 +390,18 @@
     method @IntRange(from=0L) public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function1<? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function1<? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function1<? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function1<? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function1<? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function1<? super java.lang.Float,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property @IntRange(from=0L) public final int capacity;
     property @IntRange(from=0L) public final int size;
@@ -326,6 +440,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -334,9 +460,17 @@
   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.IntFloatMap intFloatMapOf(int key1, float value1);
+    method public static androidx.collection.IntFloatMap intFloatMapOf(int key1, float value1, int key2, float value2);
+    method public static androidx.collection.IntFloatMap intFloatMapOf(int key1, float value1, int key2, float value2, int key3, float value3);
+    method public static androidx.collection.IntFloatMap intFloatMapOf(int key1, float value1, int key2, float value2, int key3, float value3, int key4, float value4);
+    method public static androidx.collection.IntFloatMap intFloatMapOf(int key1, float value1, int key2, float value2, int key3, float value3, int key4, float value4, int key5, float value5);
     method public static androidx.collection.MutableIntFloatMap mutableIntFloatMapOf();
-    method public static androidx.collection.MutableIntFloatMap mutableIntFloatMapOf(kotlin.Pair<java.lang.Integer,java.lang.Float>... pairs);
+    method public static androidx.collection.MutableIntFloatMap mutableIntFloatMapOf(int key1, float value1);
+    method public static androidx.collection.MutableIntFloatMap mutableIntFloatMapOf(int key1, float value1, int key2, float value2);
+    method public static androidx.collection.MutableIntFloatMap mutableIntFloatMapOf(int key1, float value1, int key2, float value2, int key3, float value3);
+    method public static androidx.collection.MutableIntFloatMap mutableIntFloatMapOf(int key1, float value1, int key2, float value2, int key3, float value3, int key4, float value4);
+    method public static androidx.collection.MutableIntFloatMap mutableIntFloatMapOf(int key1, float value1, int key2, float value2, int key3, float value3, int key4, float value4, int key5, float value5);
   }
 
   public abstract sealed class IntIntMap {
@@ -358,6 +492,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -366,9 +512,27 @@
   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.IntIntMap intIntMapOf(int key1, int value1);
+    method public static androidx.collection.IntIntMap intIntMapOf(int key1, int value1, int key2, int value2);
+    method public static androidx.collection.IntIntMap intIntMapOf(int key1, int value1, int key2, int value2, int key3, int value3);
+    method public static androidx.collection.IntIntMap intIntMapOf(int key1, int value1, int key2, int value2, int key3, int value3, int key4, int value4);
+    method public static androidx.collection.IntIntMap intIntMapOf(int key1, int value1, int key2, int value2, int key3, int value3, int key4, int value4, int key5, int value5);
     method public static androidx.collection.MutableIntIntMap mutableIntIntMapOf();
-    method public static androidx.collection.MutableIntIntMap mutableIntIntMapOf(kotlin.Pair<java.lang.Integer,java.lang.Integer>... pairs);
+    method public static androidx.collection.MutableIntIntMap mutableIntIntMapOf(int key1, int value1);
+    method public static androidx.collection.MutableIntIntMap mutableIntIntMapOf(int key1, int value1, int key2, int value2);
+    method public static androidx.collection.MutableIntIntMap mutableIntIntMapOf(int key1, int value1, int key2, int value2, int key3, int value3);
+    method public static androidx.collection.MutableIntIntMap mutableIntIntMapOf(int key1, int value1, int key2, int value2, int key3, int value3, int key4, int value4);
+    method public static androidx.collection.MutableIntIntMap mutableIntIntMapOf(int key1, int value1, int key2, int value2, int key3, int value3, int key4, int value4, int key5, int value5);
+  }
+
+  @kotlin.jvm.JvmInline public final value class IntIntPair {
+    ctor public IntIntPair(int first, int second);
+    method public inline operator int component1();
+    method public inline operator int component2();
+    method public int getFirst();
+    method public int getSecond();
+    property public final int first;
+    property public final int second;
   }
 
   public abstract sealed class IntList {
@@ -399,6 +563,18 @@
     method public final inline int indexOfLast(kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> predicate);
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.CharSequence> transform);
     method public final int last();
     method public final inline int last(kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> predicate);
     method public final int lastIndexOf(int element);
@@ -442,6 +618,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -450,9 +638,17 @@
   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.IntLongMap intLongMapOf(int key1, long value1);
+    method public static androidx.collection.IntLongMap intLongMapOf(int key1, long value1, int key2, long value2);
+    method public static androidx.collection.IntLongMap intLongMapOf(int key1, long value1, int key2, long value2, int key3, long value3);
+    method public static androidx.collection.IntLongMap intLongMapOf(int key1, long value1, int key2, long value2, int key3, long value3, int key4, long value4);
+    method public static androidx.collection.IntLongMap intLongMapOf(int key1, long value1, int key2, long value2, int key3, long value3, int key4, long value4, int key5, long value5);
     method public static androidx.collection.MutableIntLongMap mutableIntLongMapOf();
-    method public static androidx.collection.MutableIntLongMap mutableIntLongMapOf(kotlin.Pair<java.lang.Integer,java.lang.Long>... pairs);
+    method public static androidx.collection.MutableIntLongMap mutableIntLongMapOf(int key1, long value1);
+    method public static androidx.collection.MutableIntLongMap mutableIntLongMapOf(int key1, long value1, int key2, long value2);
+    method public static androidx.collection.MutableIntLongMap mutableIntLongMapOf(int key1, long value1, int key2, long value2, int key3, long value3);
+    method public static androidx.collection.MutableIntLongMap mutableIntLongMapOf(int key1, long value1, int key2, long value2, int key3, long value3, int key4, long value4);
+    method public static androidx.collection.MutableIntLongMap mutableIntLongMapOf(int key1, long value1, int key2, long value2, int key3, long value3, int key4, long value4, int key5, long value5);
   }
 
   public abstract sealed class IntObjectMap<V> {
@@ -474,6 +670,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -482,9 +690,17 @@
   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.IntObjectMap<V> intObjectMapOf(int key1, V value1);
+    method public static <V> androidx.collection.IntObjectMap<V> intObjectMapOf(int key1, V value1, int key2, V value2);
+    method public static <V> androidx.collection.IntObjectMap<V> intObjectMapOf(int key1, V value1, int key2, V value2, int key3, V value3);
+    method public static <V> androidx.collection.IntObjectMap<V> intObjectMapOf(int key1, V value1, int key2, V value2, int key3, V value3, int key4, V value4);
+    method public static <V> androidx.collection.IntObjectMap<V> intObjectMapOf(int key1, V value1, int key2, V value2, int key3, V value3, int key4, V value4, int key5, V value5);
     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);
+    method public static <V> androidx.collection.MutableIntObjectMap<V> mutableIntObjectMapOf(int key1, V value1);
+    method public static <V> androidx.collection.MutableIntObjectMap<V> mutableIntObjectMapOf(int key1, V value1, int key2, V value2);
+    method public static <V> androidx.collection.MutableIntObjectMap<V> mutableIntObjectMapOf(int key1, V value1, int key2, V value2, int key3, V value3);
+    method public static <V> androidx.collection.MutableIntObjectMap<V> mutableIntObjectMapOf(int key1, V value1, int key2, V value2, int key3, V value3, int key4, V value4);
+    method public static <V> androidx.collection.MutableIntObjectMap<V> mutableIntObjectMapOf(int key1, V value1, int key2, V value2, int key3, V value3, int key4, V value4, int key5, V value5);
   }
 
   public abstract sealed class IntSet {
@@ -501,6 +717,18 @@
     method @IntRange(from=0L) public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property @IntRange(from=0L) public final int capacity;
     property @IntRange(from=0L) public final int size;
@@ -539,6 +767,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -547,9 +787,17 @@
   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.LongFloatMap longFloatMapOf(long key1, float value1);
+    method public static androidx.collection.LongFloatMap longFloatMapOf(long key1, float value1, long key2, float value2);
+    method public static androidx.collection.LongFloatMap longFloatMapOf(long key1, float value1, long key2, float value2, long key3, float value3);
+    method public static androidx.collection.LongFloatMap longFloatMapOf(long key1, float value1, long key2, float value2, long key3, float value3, long key4, float value4);
+    method public static androidx.collection.LongFloatMap longFloatMapOf(long key1, float value1, long key2, float value2, long key3, float value3, long key4, float value4, long key5, float value5);
     method public static androidx.collection.MutableLongFloatMap mutableLongFloatMapOf();
-    method public static androidx.collection.MutableLongFloatMap mutableLongFloatMapOf(kotlin.Pair<java.lang.Long,java.lang.Float>... pairs);
+    method public static androidx.collection.MutableLongFloatMap mutableLongFloatMapOf(long key1, float value1);
+    method public static androidx.collection.MutableLongFloatMap mutableLongFloatMapOf(long key1, float value1, long key2, float value2);
+    method public static androidx.collection.MutableLongFloatMap mutableLongFloatMapOf(long key1, float value1, long key2, float value2, long key3, float value3);
+    method public static androidx.collection.MutableLongFloatMap mutableLongFloatMapOf(long key1, float value1, long key2, float value2, long key3, float value3, long key4, float value4);
+    method public static androidx.collection.MutableLongFloatMap mutableLongFloatMapOf(long key1, float value1, long key2, float value2, long key3, float value3, long key4, float value4, long key5, float value5);
   }
 
   public abstract sealed class LongIntMap {
@@ -571,6 +819,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -579,9 +839,17 @@
   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.LongIntMap longIntMapOf(long key1, int value1);
+    method public static androidx.collection.LongIntMap longIntMapOf(long key1, int value1, long key2, int value2);
+    method public static androidx.collection.LongIntMap longIntMapOf(long key1, int value1, long key2, int value2, long key3, int value3);
+    method public static androidx.collection.LongIntMap longIntMapOf(long key1, int value1, long key2, int value2, long key3, int value3, long key4, int value4);
+    method public static androidx.collection.LongIntMap longIntMapOf(long key1, int value1, long key2, int value2, long key3, int value3, long key4, int value4, long key5, int value5);
     method public static androidx.collection.MutableLongIntMap mutableLongIntMapOf();
-    method public static androidx.collection.MutableLongIntMap mutableLongIntMapOf(kotlin.Pair<java.lang.Long,java.lang.Integer>... pairs);
+    method public static androidx.collection.MutableLongIntMap mutableLongIntMapOf(long key1, int value1);
+    method public static androidx.collection.MutableLongIntMap mutableLongIntMapOf(long key1, int value1, long key2, int value2);
+    method public static androidx.collection.MutableLongIntMap mutableLongIntMapOf(long key1, int value1, long key2, int value2, long key3, int value3);
+    method public static androidx.collection.MutableLongIntMap mutableLongIntMapOf(long key1, int value1, long key2, int value2, long key3, int value3, long key4, int value4);
+    method public static androidx.collection.MutableLongIntMap mutableLongIntMapOf(long key1, int value1, long key2, int value2, long key3, int value3, long key4, int value4, long key5, int value5);
   }
 
   public abstract sealed class LongList {
@@ -612,6 +880,18 @@
     method public final inline int indexOfLast(kotlin.jvm.functions.Function1<? super java.lang.Long,java.lang.Boolean> predicate);
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends java.lang.CharSequence> transform);
     method public final long last();
     method public final inline long last(kotlin.jvm.functions.Function1<? super java.lang.Long,java.lang.Boolean> predicate);
     method public final int lastIndexOf(long element);
@@ -655,6 +935,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -663,9 +955,27 @@
   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.LongLongMap longLongMapOf(long key1, long value1);
+    method public static androidx.collection.LongLongMap longLongMapOf(long key1, long value1, long key2, long value2);
+    method public static androidx.collection.LongLongMap longLongMapOf(long key1, long value1, long key2, long value2, long key3, long value3);
+    method public static androidx.collection.LongLongMap longLongMapOf(long key1, long value1, long key2, long value2, long key3, long value3, long key4, long value4);
+    method public static androidx.collection.LongLongMap longLongMapOf(long key1, long value1, long key2, long value2, long key3, long value3, long key4, long value4, long key5, long value5);
     method public static androidx.collection.MutableLongLongMap mutableLongLongMapOf();
-    method public static androidx.collection.MutableLongLongMap mutableLongLongMapOf(kotlin.Pair<java.lang.Long,java.lang.Long>... pairs);
+    method public static androidx.collection.MutableLongLongMap mutableLongLongMapOf(long key1, long value1);
+    method public static androidx.collection.MutableLongLongMap mutableLongLongMapOf(long key1, long value1, long key2, long value2);
+    method public static androidx.collection.MutableLongLongMap mutableLongLongMapOf(long key1, long value1, long key2, long value2, long key3, long value3);
+    method public static androidx.collection.MutableLongLongMap mutableLongLongMapOf(long key1, long value1, long key2, long value2, long key3, long value3, long key4, long value4);
+    method public static androidx.collection.MutableLongLongMap mutableLongLongMapOf(long key1, long value1, long key2, long value2, long key3, long value3, long key4, long value4, long key5, long value5);
+  }
+
+  public final class LongLongPair {
+    ctor public LongLongPair(long first, long second);
+    method public inline operator long component1();
+    method public inline operator long component2();
+    method public long getFirst();
+    method public long getSecond();
+    property public final long first;
+    property public final long second;
   }
 
   public abstract sealed class LongObjectMap<V> {
@@ -687,6 +997,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -695,9 +1017,17 @@
   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.LongObjectMap<V> longObjectMapOf(long key1, V value1);
+    method public static <V> androidx.collection.LongObjectMap<V> longObjectMapOf(long key1, V value1, long key2, V value2);
+    method public static <V> androidx.collection.LongObjectMap<V> longObjectMapOf(long key1, V value1, long key2, V value2, long key3, V value3);
+    method public static <V> androidx.collection.LongObjectMap<V> longObjectMapOf(long key1, V value1, long key2, V value2, long key3, V value3, long key4, V value4);
+    method public static <V> androidx.collection.LongObjectMap<V> longObjectMapOf(long key1, V value1, long key2, V value2, long key3, V value3, long key4, V value4, long key5, V value5);
     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);
+    method public static <V> androidx.collection.MutableLongObjectMap<V> mutableLongObjectMapOf(long key1, V value1);
+    method public static <V> androidx.collection.MutableLongObjectMap<V> mutableLongObjectMapOf(long key1, V value1, long key2, V value2);
+    method public static <V> androidx.collection.MutableLongObjectMap<V> mutableLongObjectMapOf(long key1, V value1, long key2, V value2, long key3, V value3);
+    method public static <V> androidx.collection.MutableLongObjectMap<V> mutableLongObjectMapOf(long key1, V value1, long key2, V value2, long key3, V value3, long key4, V value4);
+    method public static <V> androidx.collection.MutableLongObjectMap<V> mutableLongObjectMapOf(long key1, V value1, long key2, V value2, long key3, V value3, long key4, V value4, long key5, V value5);
   }
 
   public abstract sealed class LongSet {
@@ -714,6 +1044,18 @@
     method @IntRange(from=0L) public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property @IntRange(from=0L) public final int capacity;
     property @IntRange(from=0L) public final int size;
@@ -809,11 +1151,8 @@
     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);
@@ -830,11 +1169,8 @@
     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);
@@ -882,11 +1218,8 @@
     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);
@@ -903,11 +1236,8 @@
     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);
@@ -942,11 +1272,8 @@
     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);
@@ -963,11 +1290,8 @@
     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);
@@ -1015,11 +1339,8 @@
     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);
@@ -1036,11 +1357,8 @@
     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);
@@ -1075,11 +1393,8 @@
     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);
@@ -1096,11 +1411,8 @@
     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);
@@ -1148,11 +1460,8 @@
     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);
@@ -1169,11 +1478,8 @@
     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);
@@ -1209,11 +1515,8 @@
     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);
@@ -1231,11 +1534,8 @@
     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);
@@ -1305,11 +1605,8 @@
     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);
@@ -1322,6 +1619,8 @@
     method public java.util.Map<K,V> asMutableMap();
     method public void clear();
     method public inline V getOrPut(K key, kotlin.jvm.functions.Function0<? extends V> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.ObjectList<K> keys);
+    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);
@@ -1348,23 +1647,27 @@
   public final class MutableScatterSet<E> extends androidx.collection.ScatterSet<E> {
     ctor public MutableScatterSet(optional int initialCapacity);
     method public boolean add(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(Iterable<? extends E> elements);
     method public boolean addAll(kotlin.sequences.Sequence<? extends E> elements);
     method public java.util.Set<E> asMutableSet();
     method public void clear();
+    method public operator void minusAssign(androidx.collection.ObjectList<E> elements);
     method public operator void minusAssign(androidx.collection.ScatterSet<E> elements);
     method public 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(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 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(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);
@@ -1392,6 +1695,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -1400,9 +1715,17 @@
   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.MutableObjectFloatMap<K> mutableObjectFloatMapOf(K key1, float value1);
+    method public static <K> androidx.collection.MutableObjectFloatMap<K> mutableObjectFloatMapOf(K key1, float value1, K key2, float value2);
+    method public static <K> androidx.collection.MutableObjectFloatMap<K> mutableObjectFloatMapOf(K key1, float value1, K key2, float value2, K key3, float value3);
+    method public static <K> androidx.collection.MutableObjectFloatMap<K> mutableObjectFloatMapOf(K key1, float value1, K key2, float value2, K key3, float value3, K key4, float value4);
+    method public static <K> androidx.collection.MutableObjectFloatMap<K> mutableObjectFloatMapOf(K key1, float value1, K key2, float value2, K key3, float value3, K key4, float value4, K key5, float value5);
     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);
+    method public static <K> androidx.collection.ObjectFloatMap<K> objectFloatMapOf(K key1, float value1);
+    method public static <K> androidx.collection.ObjectFloatMap<K> objectFloatMapOf(K key1, float value1, K key2, float value2);
+    method public static <K> androidx.collection.ObjectFloatMap<K> objectFloatMapOf(K key1, float value1, K key2, float value2, K key3, float value3);
+    method public static <K> androidx.collection.ObjectFloatMap<K> objectFloatMapOf(K key1, float value1, K key2, float value2, K key3, float value3, K key4, float value4);
+    method public static <K> androidx.collection.ObjectFloatMap<K> objectFloatMapOf(K key1, float value1, K key2, float value2, K key3, float value3, K key4, float value4, K key5, float value5);
   }
 
   public abstract sealed class ObjectIntMap<K> {
@@ -1424,6 +1747,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -1432,9 +1767,17 @@
   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.MutableObjectIntMap<K> mutableObjectIntMapOf(K key1, int value1);
+    method public static <K> androidx.collection.MutableObjectIntMap<K> mutableObjectIntMapOf(K key1, int value1, K key2, int value2);
+    method public static <K> androidx.collection.MutableObjectIntMap<K> mutableObjectIntMapOf(K key1, int value1, K key2, int value2, K key3, int value3);
+    method public static <K> androidx.collection.MutableObjectIntMap<K> mutableObjectIntMapOf(K key1, int value1, K key2, int value2, K key3, int value3, K key4, int value4);
+    method public static <K> androidx.collection.MutableObjectIntMap<K> mutableObjectIntMapOf(K key1, int value1, K key2, int value2, K key3, int value3, K key4, int value4, K key5, int value5);
     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);
+    method public static <K> androidx.collection.ObjectIntMap<K> objectIntMapOf(K key1, int value1);
+    method public static <K> androidx.collection.ObjectIntMap<K> objectIntMapOf(K key1, int value1, K key2, int value2);
+    method public static <K> androidx.collection.ObjectIntMap<K> objectIntMapOf(K key1, int value1, K key2, int value2, K key3, int value3);
+    method public static <K> androidx.collection.ObjectIntMap<K> objectIntMapOf(K key1, int value1, K key2, int value2, K key3, int value3, K key4, int value4);
+    method public static <K> androidx.collection.ObjectIntMap<K> objectIntMapOf(K key1, int value1, K key2, int value2, K key3, int value3, K key4, int value4, K key5, int value5);
   }
 
   public abstract sealed class ObjectList<E> {
@@ -1471,6 +1814,13 @@
     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 String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, optional kotlin.jvm.functions.Function1<? super E,? extends java.lang.CharSequence>? transform);
     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);
@@ -1516,6 +1866,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -1524,39 +1886,17 @@
   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.MutableObjectLongMap<K> mutableObjectLongMapOf(K key1, long value1);
+    method public static <K> androidx.collection.MutableObjectLongMap<K> mutableObjectLongMapOf(K key1, long value1, K key2, long value2);
+    method public static <K> androidx.collection.MutableObjectLongMap<K> mutableObjectLongMapOf(K key1, long value1, K key2, long value2, K key3, long value3);
+    method public static <K> androidx.collection.MutableObjectLongMap<K> mutableObjectLongMapOf(K key1, long value1, K key2, long value2, K key3, long value3, K key4, long value4);
+    method public static <K> androidx.collection.MutableObjectLongMap<K> mutableObjectLongMapOf(K key1, long value1, K key2, long value2, K key3, long value3, K key4, long value4, K key5, long value5);
     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();
-    method public inline operator float component2();
-    method public inline float getFirst();
-    method public inline float getSecond();
-    property public final inline float first;
-    property public final inline float second;
-  }
-
-  @kotlin.jvm.JvmInline public final value class PairIntInt {
-    ctor public PairIntInt(int first, int second);
-    method public inline operator int component1();
-    method public inline operator int component2();
-    method public int getFirst();
-    method public int getSecond();
-    property public final int first;
-    property public final int second;
-  }
-
-  public final class PairLongLong {
-    ctor public PairLongLong(long first, long second);
-    method public inline operator long component1();
-    method public inline operator long component2();
-    method public long getFirst();
-    method public long getSecond();
-    property public final long first;
-    property public final long second;
+    method public static <K> androidx.collection.ObjectLongMap<K> objectLongMapOf(K key1, long value1);
+    method public static <K> androidx.collection.ObjectLongMap<K> objectLongMapOf(K key1, long value1, K key2, long value2);
+    method public static <K> androidx.collection.ObjectLongMap<K> objectLongMapOf(K key1, long value1, K key2, long value2, K key3, long value3);
+    method public static <K> androidx.collection.ObjectLongMap<K> objectLongMapOf(K key1, long value1, K key2, long value2, K key3, long value3, K key4, long value4);
+    method public static <K> androidx.collection.ObjectLongMap<K> objectLongMapOf(K key1, long value1, K key2, long value2, K key3, long value3, K key4, long value4, K key5, long value5);
   }
 
   public abstract sealed class ScatterMap<K, V> {
@@ -1579,6 +1919,13 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, optional kotlin.jvm.functions.Function2<? super K,? super V,? extends java.lang.CharSequence>? transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -1606,6 +1953,13 @@
     method @IntRange(from=0L) public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, optional kotlin.jvm.functions.Function1<? super E,? extends java.lang.CharSequence>? transform);
     method public final boolean none();
     property @IntRange(from=0L) public final int capacity;
     property @IntRange(from=0L) public final int size;
diff --git a/collection/collection/api/restricted_current.txt b/collection/collection/api/restricted_current.txt
index 098c0eb..b1ab5c9 100644
--- a/collection/collection/api/restricted_current.txt
+++ b/collection/collection/api/restricted_current.txt
@@ -115,6 +115,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Float,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -126,9 +138,27 @@
   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.FloatFloatMap floatFloatMapOf(float key1, float value1);
+    method public static androidx.collection.FloatFloatMap floatFloatMapOf(float key1, float value1, float key2, float value2);
+    method public static androidx.collection.FloatFloatMap floatFloatMapOf(float key1, float value1, float key2, float value2, float key3, float value3);
+    method public static androidx.collection.FloatFloatMap floatFloatMapOf(float key1, float value1, float key2, float value2, float key3, float value3, float key4, float value4);
+    method public static androidx.collection.FloatFloatMap floatFloatMapOf(float key1, float value1, float key2, float value2, float key3, float value3, float key4, float value4, float key5, float value5);
     method public static androidx.collection.MutableFloatFloatMap mutableFloatFloatMapOf();
-    method public static androidx.collection.MutableFloatFloatMap mutableFloatFloatMapOf(kotlin.Pair<java.lang.Float,java.lang.Float>... pairs);
+    method public static androidx.collection.MutableFloatFloatMap mutableFloatFloatMapOf(float key1, float value1);
+    method public static androidx.collection.MutableFloatFloatMap mutableFloatFloatMapOf(float key1, float value1, float key2, float value2);
+    method public static androidx.collection.MutableFloatFloatMap mutableFloatFloatMapOf(float key1, float value1, float key2, float value2, float key3, float value3);
+    method public static androidx.collection.MutableFloatFloatMap mutableFloatFloatMapOf(float key1, float value1, float key2, float value2, float key3, float value3, float key4, float value4);
+    method public static androidx.collection.MutableFloatFloatMap mutableFloatFloatMapOf(float key1, float value1, float key2, float value2, float key3, float value3, float key4, float value4, float key5, float value5);
+  }
+
+  @kotlin.jvm.JvmInline public final value class FloatFloatPair {
+    ctor public FloatFloatPair(float first, float second);
+    method public inline operator float component1();
+    method public inline operator float component2();
+    method public inline float getFirst();
+    method public inline float getSecond();
+    property public final inline float first;
+    property public final inline float second;
   }
 
   public abstract sealed class FloatIntMap {
@@ -152,6 +182,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -163,9 +205,17 @@
   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.FloatIntMap floatIntMapOf(float key1, int value1);
+    method public static androidx.collection.FloatIntMap floatIntMapOf(float key1, int value1, float key2, int value2);
+    method public static androidx.collection.FloatIntMap floatIntMapOf(float key1, int value1, float key2, int value2, float key3, int value3);
+    method public static androidx.collection.FloatIntMap floatIntMapOf(float key1, int value1, float key2, int value2, float key3, int value3, float key4, int value4);
+    method public static androidx.collection.FloatIntMap floatIntMapOf(float key1, int value1, float key2, int value2, float key3, int value3, float key4, int value4, float key5, int value5);
     method public static androidx.collection.MutableFloatIntMap mutableFloatIntMapOf();
-    method public static androidx.collection.MutableFloatIntMap mutableFloatIntMapOf(kotlin.Pair<java.lang.Float,java.lang.Integer>... pairs);
+    method public static androidx.collection.MutableFloatIntMap mutableFloatIntMapOf(float key1, int value1);
+    method public static androidx.collection.MutableFloatIntMap mutableFloatIntMapOf(float key1, int value1, float key2, int value2);
+    method public static androidx.collection.MutableFloatIntMap mutableFloatIntMapOf(float key1, int value1, float key2, int value2, float key3, int value3);
+    method public static androidx.collection.MutableFloatIntMap mutableFloatIntMapOf(float key1, int value1, float key2, int value2, float key3, int value3, float key4, int value4);
+    method public static androidx.collection.MutableFloatIntMap mutableFloatIntMapOf(float key1, int value1, float key2, int value2, float key3, int value3, float key4, int value4, float key5, int value5);
   }
 
   public abstract sealed class FloatList {
@@ -196,6 +246,18 @@
     method public final inline int indexOfLast(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Boolean> predicate);
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function1<? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function1<? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function1<? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function1<? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function1<? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function1<? super java.lang.Float,? extends java.lang.CharSequence> transform);
     method public final float last();
     method public final inline float last(kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Boolean> predicate);
     method public final int lastIndexOf(float element);
@@ -243,6 +305,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super java.lang.Float,? super java.lang.Long,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -254,9 +328,17 @@
   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.FloatLongMap floatLongMapOf(float key1, long value1);
+    method public static androidx.collection.FloatLongMap floatLongMapOf(float key1, long value1, float key2, long value2);
+    method public static androidx.collection.FloatLongMap floatLongMapOf(float key1, long value1, float key2, long value2, float key3, long value3);
+    method public static androidx.collection.FloatLongMap floatLongMapOf(float key1, long value1, float key2, long value2, float key3, long value3, float key4, long value4);
+    method public static androidx.collection.FloatLongMap floatLongMapOf(float key1, long value1, float key2, long value2, float key3, long value3, float key4, long value4, float key5, long value5);
     method public static androidx.collection.MutableFloatLongMap mutableFloatLongMapOf();
-    method public static androidx.collection.MutableFloatLongMap mutableFloatLongMapOf(kotlin.Pair<java.lang.Float,java.lang.Long>... pairs);
+    method public static androidx.collection.MutableFloatLongMap mutableFloatLongMapOf(float key1, long value1);
+    method public static androidx.collection.MutableFloatLongMap mutableFloatLongMapOf(float key1, long value1, float key2, long value2);
+    method public static androidx.collection.MutableFloatLongMap mutableFloatLongMapOf(float key1, long value1, float key2, long value2, float key3, long value3);
+    method public static androidx.collection.MutableFloatLongMap mutableFloatLongMapOf(float key1, long value1, float key2, long value2, float key3, long value3, float key4, long value4);
+    method public static androidx.collection.MutableFloatLongMap mutableFloatLongMapOf(float key1, long value1, float key2, long value2, float key3, long value3, float key4, long value4, float key5, long value5);
   }
 
   public abstract sealed class FloatObjectMap<V> {
@@ -279,6 +361,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super java.lang.Float,? super V,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -290,9 +384,17 @@
   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.FloatObjectMap<V> floatObjectMapOf(float key1, V value1);
+    method public static <V> androidx.collection.FloatObjectMap<V> floatObjectMapOf(float key1, V value1, float key2, V value2);
+    method public static <V> androidx.collection.FloatObjectMap<V> floatObjectMapOf(float key1, V value1, float key2, V value2, float key3, V value3);
+    method public static <V> androidx.collection.FloatObjectMap<V> floatObjectMapOf(float key1, V value1, float key2, V value2, float key3, V value3, float key4, V value4);
+    method public static <V> androidx.collection.FloatObjectMap<V> floatObjectMapOf(float key1, V value1, float key2, V value2, float key3, V value3, float key4, V value4, float key5, V value5);
     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);
+    method public static <V> androidx.collection.MutableFloatObjectMap<V> mutableFloatObjectMapOf(float key1, V value1);
+    method public static <V> androidx.collection.MutableFloatObjectMap<V> mutableFloatObjectMapOf(float key1, V value1, float key2, V value2);
+    method public static <V> androidx.collection.MutableFloatObjectMap<V> mutableFloatObjectMapOf(float key1, V value1, float key2, V value2, float key3, V value3);
+    method public static <V> androidx.collection.MutableFloatObjectMap<V> mutableFloatObjectMapOf(float key1, V value1, float key2, V value2, float key3, V value3, float key4, V value4);
+    method public static <V> androidx.collection.MutableFloatObjectMap<V> mutableFloatObjectMapOf(float key1, V value1, float key2, V value2, float key3, V value3, float key4, V value4, float key5, V value5);
   }
 
   public abstract sealed class FloatSet {
@@ -310,6 +412,18 @@
     method @IntRange(from=0L) public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function1<? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function1<? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function1<? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function1<? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function1<? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function1<? super java.lang.Float,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property @IntRange(from=0L) public final int capacity;
     property @IntRange(from=0L) public final int size;
@@ -352,6 +466,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Float,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -363,9 +489,17 @@
   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.IntFloatMap intFloatMapOf(int key1, float value1);
+    method public static androidx.collection.IntFloatMap intFloatMapOf(int key1, float value1, int key2, float value2);
+    method public static androidx.collection.IntFloatMap intFloatMapOf(int key1, float value1, int key2, float value2, int key3, float value3);
+    method public static androidx.collection.IntFloatMap intFloatMapOf(int key1, float value1, int key2, float value2, int key3, float value3, int key4, float value4);
+    method public static androidx.collection.IntFloatMap intFloatMapOf(int key1, float value1, int key2, float value2, int key3, float value3, int key4, float value4, int key5, float value5);
     method public static androidx.collection.MutableIntFloatMap mutableIntFloatMapOf();
-    method public static androidx.collection.MutableIntFloatMap mutableIntFloatMapOf(kotlin.Pair<java.lang.Integer,java.lang.Float>... pairs);
+    method public static androidx.collection.MutableIntFloatMap mutableIntFloatMapOf(int key1, float value1);
+    method public static androidx.collection.MutableIntFloatMap mutableIntFloatMapOf(int key1, float value1, int key2, float value2);
+    method public static androidx.collection.MutableIntFloatMap mutableIntFloatMapOf(int key1, float value1, int key2, float value2, int key3, float value3);
+    method public static androidx.collection.MutableIntFloatMap mutableIntFloatMapOf(int key1, float value1, int key2, float value2, int key3, float value3, int key4, float value4);
+    method public static androidx.collection.MutableIntFloatMap mutableIntFloatMapOf(int key1, float value1, int key2, float value2, int key3, float value3, int key4, float value4, int key5, float value5);
   }
 
   public abstract sealed class IntIntMap {
@@ -389,6 +523,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -400,9 +546,27 @@
   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.IntIntMap intIntMapOf(int key1, int value1);
+    method public static androidx.collection.IntIntMap intIntMapOf(int key1, int value1, int key2, int value2);
+    method public static androidx.collection.IntIntMap intIntMapOf(int key1, int value1, int key2, int value2, int key3, int value3);
+    method public static androidx.collection.IntIntMap intIntMapOf(int key1, int value1, int key2, int value2, int key3, int value3, int key4, int value4);
+    method public static androidx.collection.IntIntMap intIntMapOf(int key1, int value1, int key2, int value2, int key3, int value3, int key4, int value4, int key5, int value5);
     method public static androidx.collection.MutableIntIntMap mutableIntIntMapOf();
-    method public static androidx.collection.MutableIntIntMap mutableIntIntMapOf(kotlin.Pair<java.lang.Integer,java.lang.Integer>... pairs);
+    method public static androidx.collection.MutableIntIntMap mutableIntIntMapOf(int key1, int value1);
+    method public static androidx.collection.MutableIntIntMap mutableIntIntMapOf(int key1, int value1, int key2, int value2);
+    method public static androidx.collection.MutableIntIntMap mutableIntIntMapOf(int key1, int value1, int key2, int value2, int key3, int value3);
+    method public static androidx.collection.MutableIntIntMap mutableIntIntMapOf(int key1, int value1, int key2, int value2, int key3, int value3, int key4, int value4);
+    method public static androidx.collection.MutableIntIntMap mutableIntIntMapOf(int key1, int value1, int key2, int value2, int key3, int value3, int key4, int value4, int key5, int value5);
+  }
+
+  @kotlin.jvm.JvmInline public final value class IntIntPair {
+    ctor public IntIntPair(int first, int second);
+    method public inline operator int component1();
+    method public inline operator int component2();
+    method public int getFirst();
+    method public int getSecond();
+    property public final int first;
+    property public final int second;
   }
 
   public abstract sealed class IntList {
@@ -433,6 +597,18 @@
     method public final inline int indexOfLast(kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> predicate);
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.CharSequence> transform);
     method public final int last();
     method public final inline int last(kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Boolean> predicate);
     method public final int lastIndexOf(int element);
@@ -480,6 +656,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -491,9 +679,17 @@
   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.IntLongMap intLongMapOf(int key1, long value1);
+    method public static androidx.collection.IntLongMap intLongMapOf(int key1, long value1, int key2, long value2);
+    method public static androidx.collection.IntLongMap intLongMapOf(int key1, long value1, int key2, long value2, int key3, long value3);
+    method public static androidx.collection.IntLongMap intLongMapOf(int key1, long value1, int key2, long value2, int key3, long value3, int key4, long value4);
+    method public static androidx.collection.IntLongMap intLongMapOf(int key1, long value1, int key2, long value2, int key3, long value3, int key4, long value4, int key5, long value5);
     method public static androidx.collection.MutableIntLongMap mutableIntLongMapOf();
-    method public static androidx.collection.MutableIntLongMap mutableIntLongMapOf(kotlin.Pair<java.lang.Integer,java.lang.Long>... pairs);
+    method public static androidx.collection.MutableIntLongMap mutableIntLongMapOf(int key1, long value1);
+    method public static androidx.collection.MutableIntLongMap mutableIntLongMapOf(int key1, long value1, int key2, long value2);
+    method public static androidx.collection.MutableIntLongMap mutableIntLongMapOf(int key1, long value1, int key2, long value2, int key3, long value3);
+    method public static androidx.collection.MutableIntLongMap mutableIntLongMapOf(int key1, long value1, int key2, long value2, int key3, long value3, int key4, long value4);
+    method public static androidx.collection.MutableIntLongMap mutableIntLongMapOf(int key1, long value1, int key2, long value2, int key3, long value3, int key4, long value4, int key5, long value5);
   }
 
   public abstract sealed class IntObjectMap<V> {
@@ -516,6 +712,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super java.lang.Integer,? super V,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -527,9 +735,17 @@
   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.IntObjectMap<V> intObjectMapOf(int key1, V value1);
+    method public static <V> androidx.collection.IntObjectMap<V> intObjectMapOf(int key1, V value1, int key2, V value2);
+    method public static <V> androidx.collection.IntObjectMap<V> intObjectMapOf(int key1, V value1, int key2, V value2, int key3, V value3);
+    method public static <V> androidx.collection.IntObjectMap<V> intObjectMapOf(int key1, V value1, int key2, V value2, int key3, V value3, int key4, V value4);
+    method public static <V> androidx.collection.IntObjectMap<V> intObjectMapOf(int key1, V value1, int key2, V value2, int key3, V value3, int key4, V value4, int key5, V value5);
     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);
+    method public static <V> androidx.collection.MutableIntObjectMap<V> mutableIntObjectMapOf(int key1, V value1);
+    method public static <V> androidx.collection.MutableIntObjectMap<V> mutableIntObjectMapOf(int key1, V value1, int key2, V value2);
+    method public static <V> androidx.collection.MutableIntObjectMap<V> mutableIntObjectMapOf(int key1, V value1, int key2, V value2, int key3, V value3);
+    method public static <V> androidx.collection.MutableIntObjectMap<V> mutableIntObjectMapOf(int key1, V value1, int key2, V value2, int key3, V value3, int key4, V value4);
+    method public static <V> androidx.collection.MutableIntObjectMap<V> mutableIntObjectMapOf(int key1, V value1, int key2, V value2, int key3, V value3, int key4, V value4, int key5, V value5);
   }
 
   public abstract sealed class IntSet {
@@ -547,6 +763,18 @@
     method @IntRange(from=0L) public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property @IntRange(from=0L) public final int capacity;
     property @IntRange(from=0L) public final int size;
@@ -589,6 +817,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Float,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -600,9 +840,17 @@
   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.LongFloatMap longFloatMapOf(long key1, float value1);
+    method public static androidx.collection.LongFloatMap longFloatMapOf(long key1, float value1, long key2, float value2);
+    method public static androidx.collection.LongFloatMap longFloatMapOf(long key1, float value1, long key2, float value2, long key3, float value3);
+    method public static androidx.collection.LongFloatMap longFloatMapOf(long key1, float value1, long key2, float value2, long key3, float value3, long key4, float value4);
+    method public static androidx.collection.LongFloatMap longFloatMapOf(long key1, float value1, long key2, float value2, long key3, float value3, long key4, float value4, long key5, float value5);
     method public static androidx.collection.MutableLongFloatMap mutableLongFloatMapOf();
-    method public static androidx.collection.MutableLongFloatMap mutableLongFloatMapOf(kotlin.Pair<java.lang.Long,java.lang.Float>... pairs);
+    method public static androidx.collection.MutableLongFloatMap mutableLongFloatMapOf(long key1, float value1);
+    method public static androidx.collection.MutableLongFloatMap mutableLongFloatMapOf(long key1, float value1, long key2, float value2);
+    method public static androidx.collection.MutableLongFloatMap mutableLongFloatMapOf(long key1, float value1, long key2, float value2, long key3, float value3);
+    method public static androidx.collection.MutableLongFloatMap mutableLongFloatMapOf(long key1, float value1, long key2, float value2, long key3, float value3, long key4, float value4);
+    method public static androidx.collection.MutableLongFloatMap mutableLongFloatMapOf(long key1, float value1, long key2, float value2, long key3, float value3, long key4, float value4, long key5, float value5);
   }
 
   public abstract sealed class LongIntMap {
@@ -626,6 +874,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -637,9 +897,17 @@
   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.LongIntMap longIntMapOf(long key1, int value1);
+    method public static androidx.collection.LongIntMap longIntMapOf(long key1, int value1, long key2, int value2);
+    method public static androidx.collection.LongIntMap longIntMapOf(long key1, int value1, long key2, int value2, long key3, int value3);
+    method public static androidx.collection.LongIntMap longIntMapOf(long key1, int value1, long key2, int value2, long key3, int value3, long key4, int value4);
+    method public static androidx.collection.LongIntMap longIntMapOf(long key1, int value1, long key2, int value2, long key3, int value3, long key4, int value4, long key5, int value5);
     method public static androidx.collection.MutableLongIntMap mutableLongIntMapOf();
-    method public static androidx.collection.MutableLongIntMap mutableLongIntMapOf(kotlin.Pair<java.lang.Long,java.lang.Integer>... pairs);
+    method public static androidx.collection.MutableLongIntMap mutableLongIntMapOf(long key1, int value1);
+    method public static androidx.collection.MutableLongIntMap mutableLongIntMapOf(long key1, int value1, long key2, int value2);
+    method public static androidx.collection.MutableLongIntMap mutableLongIntMapOf(long key1, int value1, long key2, int value2, long key3, int value3);
+    method public static androidx.collection.MutableLongIntMap mutableLongIntMapOf(long key1, int value1, long key2, int value2, long key3, int value3, long key4, int value4);
+    method public static androidx.collection.MutableLongIntMap mutableLongIntMapOf(long key1, int value1, long key2, int value2, long key3, int value3, long key4, int value4, long key5, int value5);
   }
 
   public abstract sealed class LongList {
@@ -670,6 +938,18 @@
     method public final inline int indexOfLast(kotlin.jvm.functions.Function1<? super java.lang.Long,java.lang.Boolean> predicate);
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends java.lang.CharSequence> transform);
     method public final long last();
     method public final inline long last(kotlin.jvm.functions.Function1<? super java.lang.Long,java.lang.Boolean> predicate);
     method public final int lastIndexOf(long element);
@@ -717,6 +997,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super java.lang.Long,? super java.lang.Long,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -728,9 +1020,27 @@
   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.LongLongMap longLongMapOf(long key1, long value1);
+    method public static androidx.collection.LongLongMap longLongMapOf(long key1, long value1, long key2, long value2);
+    method public static androidx.collection.LongLongMap longLongMapOf(long key1, long value1, long key2, long value2, long key3, long value3);
+    method public static androidx.collection.LongLongMap longLongMapOf(long key1, long value1, long key2, long value2, long key3, long value3, long key4, long value4);
+    method public static androidx.collection.LongLongMap longLongMapOf(long key1, long value1, long key2, long value2, long key3, long value3, long key4, long value4, long key5, long value5);
     method public static androidx.collection.MutableLongLongMap mutableLongLongMapOf();
-    method public static androidx.collection.MutableLongLongMap mutableLongLongMapOf(kotlin.Pair<java.lang.Long,java.lang.Long>... pairs);
+    method public static androidx.collection.MutableLongLongMap mutableLongLongMapOf(long key1, long value1);
+    method public static androidx.collection.MutableLongLongMap mutableLongLongMapOf(long key1, long value1, long key2, long value2);
+    method public static androidx.collection.MutableLongLongMap mutableLongLongMapOf(long key1, long value1, long key2, long value2, long key3, long value3);
+    method public static androidx.collection.MutableLongLongMap mutableLongLongMapOf(long key1, long value1, long key2, long value2, long key3, long value3, long key4, long value4);
+    method public static androidx.collection.MutableLongLongMap mutableLongLongMapOf(long key1, long value1, long key2, long value2, long key3, long value3, long key4, long value4, long key5, long value5);
+  }
+
+  public final class LongLongPair {
+    ctor public LongLongPair(long first, long second);
+    method public inline operator long component1();
+    method public inline operator long component2();
+    method public long getFirst();
+    method public long getSecond();
+    property public final long first;
+    property public final long second;
   }
 
   public abstract sealed class LongObjectMap<V> {
@@ -753,6 +1063,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super java.lang.Long,? super V,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -764,9 +1086,17 @@
   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.LongObjectMap<V> longObjectMapOf(long key1, V value1);
+    method public static <V> androidx.collection.LongObjectMap<V> longObjectMapOf(long key1, V value1, long key2, V value2);
+    method public static <V> androidx.collection.LongObjectMap<V> longObjectMapOf(long key1, V value1, long key2, V value2, long key3, V value3);
+    method public static <V> androidx.collection.LongObjectMap<V> longObjectMapOf(long key1, V value1, long key2, V value2, long key3, V value3, long key4, V value4);
+    method public static <V> androidx.collection.LongObjectMap<V> longObjectMapOf(long key1, V value1, long key2, V value2, long key3, V value3, long key4, V value4, long key5, V value5);
     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);
+    method public static <V> androidx.collection.MutableLongObjectMap<V> mutableLongObjectMapOf(long key1, V value1);
+    method public static <V> androidx.collection.MutableLongObjectMap<V> mutableLongObjectMapOf(long key1, V value1, long key2, V value2);
+    method public static <V> androidx.collection.MutableLongObjectMap<V> mutableLongObjectMapOf(long key1, V value1, long key2, V value2, long key3, V value3);
+    method public static <V> androidx.collection.MutableLongObjectMap<V> mutableLongObjectMapOf(long key1, V value1, long key2, V value2, long key3, V value3, long key4, V value4);
+    method public static <V> androidx.collection.MutableLongObjectMap<V> mutableLongObjectMapOf(long key1, V value1, long key2, V value2, long key3, V value3, long key4, V value4, long key5, V value5);
   }
 
   public abstract sealed class LongSet {
@@ -784,6 +1114,18 @@
     method @IntRange(from=0L) public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property @IntRange(from=0L) public final int capacity;
     property @IntRange(from=0L) public final int size;
@@ -881,11 +1223,8 @@
     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);
@@ -902,11 +1241,8 @@
     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);
@@ -954,11 +1290,8 @@
     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);
@@ -975,11 +1308,8 @@
     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);
@@ -1014,11 +1344,8 @@
     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);
@@ -1035,11 +1362,8 @@
     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);
@@ -1087,11 +1411,8 @@
     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);
@@ -1108,11 +1429,8 @@
     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);
@@ -1147,11 +1465,8 @@
     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);
@@ -1168,11 +1483,8 @@
     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);
@@ -1220,11 +1532,8 @@
     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);
@@ -1241,11 +1550,8 @@
     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);
@@ -1281,11 +1587,8 @@
     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);
@@ -1303,11 +1606,8 @@
     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);
@@ -1377,11 +1677,8 @@
     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);
@@ -1394,6 +1691,8 @@
     method public java.util.Map<K,V> asMutableMap();
     method public void clear();
     method public inline V getOrPut(K key, kotlin.jvm.functions.Function0<? extends V> defaultValue);
+    method public inline operator void minusAssign(androidx.collection.ObjectList<K> keys);
+    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);
@@ -1420,23 +1719,27 @@
   public final class MutableScatterSet<E> extends androidx.collection.ScatterSet<E> {
     ctor public MutableScatterSet(optional int initialCapacity);
     method public boolean add(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(Iterable<? extends E> elements);
     method public boolean addAll(kotlin.sequences.Sequence<? extends E> elements);
     method public java.util.Set<E> asMutableSet();
     method public void clear();
+    method public operator void minusAssign(androidx.collection.ObjectList<E> elements);
     method public operator void minusAssign(androidx.collection.ScatterSet<E> elements);
     method public 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(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 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(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);
@@ -1467,6 +1770,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super K,? super java.lang.Float,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -1478,9 +1793,17 @@
   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.MutableObjectFloatMap<K> mutableObjectFloatMapOf(K key1, float value1);
+    method public static <K> androidx.collection.MutableObjectFloatMap<K> mutableObjectFloatMapOf(K key1, float value1, K key2, float value2);
+    method public static <K> androidx.collection.MutableObjectFloatMap<K> mutableObjectFloatMapOf(K key1, float value1, K key2, float value2, K key3, float value3);
+    method public static <K> androidx.collection.MutableObjectFloatMap<K> mutableObjectFloatMapOf(K key1, float value1, K key2, float value2, K key3, float value3, K key4, float value4);
+    method public static <K> androidx.collection.MutableObjectFloatMap<K> mutableObjectFloatMapOf(K key1, float value1, K key2, float value2, K key3, float value3, K key4, float value4, K key5, float value5);
     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);
+    method public static <K> androidx.collection.ObjectFloatMap<K> objectFloatMapOf(K key1, float value1);
+    method public static <K> androidx.collection.ObjectFloatMap<K> objectFloatMapOf(K key1, float value1, K key2, float value2);
+    method public static <K> androidx.collection.ObjectFloatMap<K> objectFloatMapOf(K key1, float value1, K key2, float value2, K key3, float value3);
+    method public static <K> androidx.collection.ObjectFloatMap<K> objectFloatMapOf(K key1, float value1, K key2, float value2, K key3, float value3, K key4, float value4);
+    method public static <K> androidx.collection.ObjectFloatMap<K> objectFloatMapOf(K key1, float value1, K key2, float value2, K key3, float value3, K key4, float value4, K key5, float value5);
   }
 
   public abstract sealed class ObjectIntMap<K> {
@@ -1504,6 +1827,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super K,? super java.lang.Integer,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -1515,9 +1850,17 @@
   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.MutableObjectIntMap<K> mutableObjectIntMapOf(K key1, int value1);
+    method public static <K> androidx.collection.MutableObjectIntMap<K> mutableObjectIntMapOf(K key1, int value1, K key2, int value2);
+    method public static <K> androidx.collection.MutableObjectIntMap<K> mutableObjectIntMapOf(K key1, int value1, K key2, int value2, K key3, int value3);
+    method public static <K> androidx.collection.MutableObjectIntMap<K> mutableObjectIntMapOf(K key1, int value1, K key2, int value2, K key3, int value3, K key4, int value4);
+    method public static <K> androidx.collection.MutableObjectIntMap<K> mutableObjectIntMapOf(K key1, int value1, K key2, int value2, K key3, int value3, K key4, int value4, K key5, int value5);
     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);
+    method public static <K> androidx.collection.ObjectIntMap<K> objectIntMapOf(K key1, int value1);
+    method public static <K> androidx.collection.ObjectIntMap<K> objectIntMapOf(K key1, int value1, K key2, int value2);
+    method public static <K> androidx.collection.ObjectIntMap<K> objectIntMapOf(K key1, int value1, K key2, int value2, K key3, int value3);
+    method public static <K> androidx.collection.ObjectIntMap<K> objectIntMapOf(K key1, int value1, K key2, int value2, K key3, int value3, K key4, int value4);
+    method public static <K> androidx.collection.ObjectIntMap<K> objectIntMapOf(K key1, int value1, K key2, int value2, K key3, int value3, K key4, int value4, K key5, int value5);
   }
 
   public abstract sealed class ObjectList<E> {
@@ -1554,6 +1897,13 @@
     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 String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, optional kotlin.jvm.functions.Function1<? super E,? extends java.lang.CharSequence>? transform);
     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);
@@ -1603,6 +1953,18 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, optional CharSequence prefix, kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(optional CharSequence separator, kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,? extends java.lang.CharSequence> transform);
+    method public final inline String joinToString(kotlin.jvm.functions.Function2<? super K,? super java.lang.Long,? extends java.lang.CharSequence> transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -1614,39 +1976,17 @@
   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.MutableObjectLongMap<K> mutableObjectLongMapOf(K key1, long value1);
+    method public static <K> androidx.collection.MutableObjectLongMap<K> mutableObjectLongMapOf(K key1, long value1, K key2, long value2);
+    method public static <K> androidx.collection.MutableObjectLongMap<K> mutableObjectLongMapOf(K key1, long value1, K key2, long value2, K key3, long value3);
+    method public static <K> androidx.collection.MutableObjectLongMap<K> mutableObjectLongMapOf(K key1, long value1, K key2, long value2, K key3, long value3, K key4, long value4);
+    method public static <K> androidx.collection.MutableObjectLongMap<K> mutableObjectLongMapOf(K key1, long value1, K key2, long value2, K key3, long value3, K key4, long value4, K key5, long value5);
     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();
-    method public inline operator float component2();
-    method public inline float getFirst();
-    method public inline float getSecond();
-    property public final inline float first;
-    property public final inline float second;
-  }
-
-  @kotlin.jvm.JvmInline public final value class PairIntInt {
-    ctor public PairIntInt(int first, int second);
-    method public inline operator int component1();
-    method public inline operator int component2();
-    method public int getFirst();
-    method public int getSecond();
-    property public final int first;
-    property public final int second;
-  }
-
-  public final class PairLongLong {
-    ctor public PairLongLong(long first, long second);
-    method public inline operator long component1();
-    method public inline operator long component2();
-    method public long getFirst();
-    method public long getSecond();
-    property public final long first;
-    property public final long second;
+    method public static <K> androidx.collection.ObjectLongMap<K> objectLongMapOf(K key1, long value1);
+    method public static <K> androidx.collection.ObjectLongMap<K> objectLongMapOf(K key1, long value1, K key2, long value2);
+    method public static <K> androidx.collection.ObjectLongMap<K> objectLongMapOf(K key1, long value1, K key2, long value2, K key3, long value3);
+    method public static <K> androidx.collection.ObjectLongMap<K> objectLongMapOf(K key1, long value1, K key2, long value2, K key3, long value3, K key4, long value4);
+    method public static <K> androidx.collection.ObjectLongMap<K> objectLongMapOf(K key1, long value1, K key2, long value2, K key3, long value3, K key4, long value4, K key5, long value5);
   }
 
   public abstract sealed class ScatterMap<K, V> {
@@ -1670,6 +2010,13 @@
     method public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, optional kotlin.jvm.functions.Function2<? super K,? super V,? extends java.lang.CharSequence>? transform);
     method public final boolean none();
     property public final int capacity;
     property public final int size;
@@ -1709,6 +2056,13 @@
     method @IntRange(from=0L) public final int getSize();
     method public final boolean isEmpty();
     method public final boolean isNotEmpty();
+    method public final String joinToString();
+    method public final String joinToString(optional CharSequence separator);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated);
+    method public final String joinToString(optional CharSequence separator, optional CharSequence prefix, optional CharSequence postfix, optional int limit, optional CharSequence truncated, optional kotlin.jvm.functions.Function1<? super E,? extends java.lang.CharSequence>? transform);
     method public final boolean none();
     property @IntRange(from=0L) public final int capacity;
     property @IntRange(from=0L) public final int size;
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatFloatMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatFloatMap.kt
index bca375a..5fd9383 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatFloatMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatFloatMap.kt
@@ -22,6 +22,7 @@
 package androidx.collection
 
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -41,7 +42,6 @@
 /**
  * Returns an empty, read-only [FloatFloatMap].
  */
-@Suppress("UNCHECKED_CAST")
 public fun emptyFloatFloatMap(): FloatFloatMap = EmptyFloatFloatMap
 
 /**
@@ -50,18 +50,87 @@
 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.
+ * Returns a new [FloatFloatMap] with [key1] associated with [value1].
  */
-public fun floatFloatMapOf(vararg pairs: Pair<Float, Float>): FloatFloatMap =
-    MutableFloatFloatMap(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun floatFloatMapOf(
+    key1: Float,
+    value1: Float
+): FloatFloatMap = MutableFloatFloatMap().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [FloatFloatMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun floatFloatMapOf(
+    key1: Float,
+    value1: Float,
+    key2: Float,
+    value2: Float,
+): FloatFloatMap = MutableFloatFloatMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [FloatFloatMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun floatFloatMapOf(
+    key1: Float,
+    value1: Float,
+    key2: Float,
+    value2: Float,
+    key3: Float,
+    value3: Float,
+): FloatFloatMap = MutableFloatFloatMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [FloatFloatMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun floatFloatMapOf(
+    key1: Float,
+    value1: Float,
+    key2: Float,
+    value2: Float,
+    key3: Float,
+    value3: Float,
+    key4: Float,
+    value4: Float,
+): FloatFloatMap = MutableFloatFloatMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [FloatFloatMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun floatFloatMapOf(
+    key1: Float,
+    value1: Float,
+    key2: Float,
+    value2: Float,
+    key3: Float,
+    value3: Float,
+    key4: Float,
+    value4: Float,
+    key5: Float,
+    value5: Float,
+): FloatFloatMap = MutableFloatFloatMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -70,18 +139,87 @@
 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.
+ * Returns a new [MutableFloatFloatMap] with [key1] associated with [value1].
  */
-public fun mutableFloatFloatMapOf(vararg pairs: Pair<Float, Float>): MutableFloatFloatMap =
-    MutableFloatFloatMap(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun mutableFloatFloatMapOf(
+    key1: Float,
+    value1: Float
+): MutableFloatFloatMap = MutableFloatFloatMap().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [MutableFloatFloatMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun mutableFloatFloatMapOf(
+    key1: Float,
+    value1: Float,
+    key2: Float,
+    value2: Float,
+): MutableFloatFloatMap = MutableFloatFloatMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [MutableFloatFloatMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun mutableFloatFloatMapOf(
+    key1: Float,
+    value1: Float,
+    key2: Float,
+    value2: Float,
+    key3: Float,
+    value3: Float,
+): MutableFloatFloatMap = MutableFloatFloatMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [MutableFloatFloatMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun mutableFloatFloatMapOf(
+    key1: Float,
+    value1: Float,
+    key2: Float,
+    value2: Float,
+    key3: Float,
+    value3: Float,
+    key4: Float,
+    value4: Float,
+): MutableFloatFloatMap = MutableFloatFloatMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [MutableFloatFloatMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun mutableFloatFloatMapOf(
+    key1: Float,
+    value1: Float,
+    key2: Float,
+    value2: Float,
+    key3: Float,
+    value3: Float,
+    key4: Float,
+    value4: Float,
+    key5: Float,
+    value5: Float,
+): MutableFloatFloatMap = MutableFloatFloatMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -336,6 +474,73 @@
     }
 
     /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@FloatFloatMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(key)
+            append('=')
+            append(value)
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. Each entry is created with [transform].
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (key: Float, value: Float) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@FloatFloatMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(key, value))
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
      * Returns the hash code value for this map. The hash code the sum of the hash
      * codes of each key/value pair.
      */
@@ -554,20 +759,6 @@
     }
 
     /**
-     * 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) {
@@ -577,29 +768,6 @@
     }
 
     /**
-     * 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)
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/PairFloatFloat.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatFloatPair.kt
similarity index 93%
rename from collection/collection/src/commonMain/kotlin/androidx/collection/PairFloatFloat.kt
rename to collection/collection/src/commonMain/kotlin/androidx/collection/FloatFloatPair.kt
index aac435a..602efb3 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/PairFloatFloat.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatFloatPair.kt
@@ -26,14 +26,14 @@
  * *Note*: This class is optimized by using a value class, a Kotlin language featured
  * not available from Java code. Java developers can get the same functionality by
  * using [Pair] or by constructing a custom implementation using Float parameters
- * directly (see [PairLongLong] for an example).
+ * directly (see [LongLongPair] for an example).
  */
 @JvmInline
-public value class PairFloatFloat internal constructor(
+public value class FloatFloatPair internal constructor(
     @PublishedApi @JvmField internal val packedValue: Long
 ) {
     /**
-     * Constructs a [PairFloatFloat] with two [Float] values.
+     * Constructs a [FloatFloatPair] with two [Float] values.
      *
      * @param first the first value in the pair
      * @param second the second value in the pair
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatIntMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatIntMap.kt
index 29adea3..3980c9c 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatIntMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatIntMap.kt
@@ -22,6 +22,7 @@
 package androidx.collection
 
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -41,7 +42,6 @@
 /**
  * Returns an empty, read-only [FloatIntMap].
  */
-@Suppress("UNCHECKED_CAST")
 public fun emptyFloatIntMap(): FloatIntMap = EmptyFloatIntMap
 
 /**
@@ -50,18 +50,87 @@
 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.
+ * Returns a new [FloatIntMap] with [key1] associated with [value1].
  */
-public fun floatIntMapOf(vararg pairs: Pair<Float, Int>): FloatIntMap =
-    MutableFloatIntMap(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun floatIntMapOf(
+    key1: Float,
+    value1: Int
+): FloatIntMap = MutableFloatIntMap().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [FloatIntMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun floatIntMapOf(
+    key1: Float,
+    value1: Int,
+    key2: Float,
+    value2: Int,
+): FloatIntMap = MutableFloatIntMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [FloatIntMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun floatIntMapOf(
+    key1: Float,
+    value1: Int,
+    key2: Float,
+    value2: Int,
+    key3: Float,
+    value3: Int,
+): FloatIntMap = MutableFloatIntMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [FloatIntMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun floatIntMapOf(
+    key1: Float,
+    value1: Int,
+    key2: Float,
+    value2: Int,
+    key3: Float,
+    value3: Int,
+    key4: Float,
+    value4: Int,
+): FloatIntMap = MutableFloatIntMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [FloatIntMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun floatIntMapOf(
+    key1: Float,
+    value1: Int,
+    key2: Float,
+    value2: Int,
+    key3: Float,
+    value3: Int,
+    key4: Float,
+    value4: Int,
+    key5: Float,
+    value5: Int,
+): FloatIntMap = MutableFloatIntMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -70,18 +139,87 @@
 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.
+ * Returns a new [MutableFloatIntMap] with [key1] associated with [value1].
  */
-public fun mutableFloatIntMapOf(vararg pairs: Pair<Float, Int>): MutableFloatIntMap =
-    MutableFloatIntMap(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun mutableFloatIntMapOf(
+    key1: Float,
+    value1: Int
+): MutableFloatIntMap = MutableFloatIntMap().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [MutableFloatIntMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun mutableFloatIntMapOf(
+    key1: Float,
+    value1: Int,
+    key2: Float,
+    value2: Int,
+): MutableFloatIntMap = MutableFloatIntMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [MutableFloatIntMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun mutableFloatIntMapOf(
+    key1: Float,
+    value1: Int,
+    key2: Float,
+    value2: Int,
+    key3: Float,
+    value3: Int,
+): MutableFloatIntMap = MutableFloatIntMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [MutableFloatIntMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun mutableFloatIntMapOf(
+    key1: Float,
+    value1: Int,
+    key2: Float,
+    value2: Int,
+    key3: Float,
+    value3: Int,
+    key4: Float,
+    value4: Int,
+): MutableFloatIntMap = MutableFloatIntMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [MutableFloatIntMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun mutableFloatIntMapOf(
+    key1: Float,
+    value1: Int,
+    key2: Float,
+    value2: Int,
+    key3: Float,
+    value3: Int,
+    key4: Float,
+    value4: Int,
+    key5: Float,
+    value5: Int,
+): MutableFloatIntMap = MutableFloatIntMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -336,6 +474,73 @@
     }
 
     /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@FloatIntMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(key)
+            append('=')
+            append(value)
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. Each entry is created with [transform].
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (key: Float, value: Int) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@FloatIntMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(key, value))
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
      * Returns the hash code value for this map. The hash code the sum of the hash
      * codes of each key/value pair.
      */
@@ -554,20 +759,6 @@
     }
 
     /**
-     * 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) {
@@ -577,29 +768,6 @@
     }
 
     /**
-     * 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)
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatList.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatList.kt
index 70b5787..391aa06 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatList.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatList.kt
@@ -21,6 +21,7 @@
 import kotlin.contracts.ExperimentalContracts
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -425,6 +426,67 @@
     }
 
     /**
+     * Creates a String from the elements separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        this@FloatList.forEachIndexed { index, element ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(element)
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the elements separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. [transform] dictates how each element will be represented.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (Float) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        this@FloatList.forEachIndexed { index, element ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(element))
+        }
+        append(postfix)
+    }
+
+    /**
      * Returns a hash code based on the contents of the [FloatList].
      */
     override fun hashCode(): Int {
@@ -457,23 +519,7 @@
      * 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(']')
-        }
-    }
+    override fun toString(): String = joinToString(prefix = "[", postfix = "]")
 }
 
 /**
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatLongMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatLongMap.kt
index ac71a3e..b48b09d 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatLongMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatLongMap.kt
@@ -22,6 +22,7 @@
 package androidx.collection
 
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -41,7 +42,6 @@
 /**
  * Returns an empty, read-only [FloatLongMap].
  */
-@Suppress("UNCHECKED_CAST")
 public fun emptyFloatLongMap(): FloatLongMap = EmptyFloatLongMap
 
 /**
@@ -50,18 +50,87 @@
 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.
+ * Returns a new [FloatLongMap] with [key1] associated with [value1].
  */
-public fun floatLongMapOf(vararg pairs: Pair<Float, Long>): FloatLongMap =
-    MutableFloatLongMap(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun floatLongMapOf(
+    key1: Float,
+    value1: Long
+): FloatLongMap = MutableFloatLongMap().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [FloatLongMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun floatLongMapOf(
+    key1: Float,
+    value1: Long,
+    key2: Float,
+    value2: Long,
+): FloatLongMap = MutableFloatLongMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [FloatLongMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun floatLongMapOf(
+    key1: Float,
+    value1: Long,
+    key2: Float,
+    value2: Long,
+    key3: Float,
+    value3: Long,
+): FloatLongMap = MutableFloatLongMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [FloatLongMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun floatLongMapOf(
+    key1: Float,
+    value1: Long,
+    key2: Float,
+    value2: Long,
+    key3: Float,
+    value3: Long,
+    key4: Float,
+    value4: Long,
+): FloatLongMap = MutableFloatLongMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [FloatLongMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun floatLongMapOf(
+    key1: Float,
+    value1: Long,
+    key2: Float,
+    value2: Long,
+    key3: Float,
+    value3: Long,
+    key4: Float,
+    value4: Long,
+    key5: Float,
+    value5: Long,
+): FloatLongMap = MutableFloatLongMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -70,18 +139,87 @@
 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.
+ * Returns a new [MutableFloatLongMap] with [key1] associated with [value1].
  */
-public fun mutableFloatLongMapOf(vararg pairs: Pair<Float, Long>): MutableFloatLongMap =
-    MutableFloatLongMap(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun mutableFloatLongMapOf(
+    key1: Float,
+    value1: Long
+): MutableFloatLongMap = MutableFloatLongMap().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [MutableFloatLongMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun mutableFloatLongMapOf(
+    key1: Float,
+    value1: Long,
+    key2: Float,
+    value2: Long,
+): MutableFloatLongMap = MutableFloatLongMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [MutableFloatLongMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun mutableFloatLongMapOf(
+    key1: Float,
+    value1: Long,
+    key2: Float,
+    value2: Long,
+    key3: Float,
+    value3: Long,
+): MutableFloatLongMap = MutableFloatLongMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [MutableFloatLongMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun mutableFloatLongMapOf(
+    key1: Float,
+    value1: Long,
+    key2: Float,
+    value2: Long,
+    key3: Float,
+    value3: Long,
+    key4: Float,
+    value4: Long,
+): MutableFloatLongMap = MutableFloatLongMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [MutableFloatLongMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun mutableFloatLongMapOf(
+    key1: Float,
+    value1: Long,
+    key2: Float,
+    value2: Long,
+    key3: Float,
+    value3: Long,
+    key4: Float,
+    value4: Long,
+    key5: Float,
+    value5: Long,
+): MutableFloatLongMap = MutableFloatLongMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -336,6 +474,73 @@
     }
 
     /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@FloatLongMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(key)
+            append('=')
+            append(value)
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. Each entry is created with [transform].
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (key: Float, value: Long) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@FloatLongMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(key, value))
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
      * Returns the hash code value for this map. The hash code the sum of the hash
      * codes of each key/value pair.
      */
@@ -554,20 +759,6 @@
     }
 
     /**
-     * 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) {
@@ -577,29 +768,6 @@
     }
 
     /**
-     * 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)
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatObjectMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatObjectMap.kt
index 292347f..e990f27 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatObjectMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatObjectMap.kt
@@ -23,6 +23,7 @@
 
 import androidx.collection.internal.EMPTY_OBJECTS
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -52,18 +53,87 @@
 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.
+ * Returns a new [FloatObjectMap] with [key1] associated with [value1].
  */
-public fun <V> floatObjectMapOf(vararg pairs: Pair<Float, V>): FloatObjectMap<V> =
-    MutableFloatObjectMap<V>(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun <V> floatObjectMapOf(
+    key1: Float,
+    value1: V
+): FloatObjectMap<V> = MutableFloatObjectMap<V>().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [FloatObjectMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun <V> floatObjectMapOf(
+    key1: Float,
+    value1: V,
+    key2: Float,
+    value2: V,
+): FloatObjectMap<V> = MutableFloatObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [FloatObjectMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun <V> floatObjectMapOf(
+    key1: Float,
+    value1: V,
+    key2: Float,
+    value2: V,
+    key3: Float,
+    value3: V,
+): FloatObjectMap<V> = MutableFloatObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [FloatObjectMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun <V> floatObjectMapOf(
+    key1: Float,
+    value1: V,
+    key2: Float,
+    value2: V,
+    key3: Float,
+    value3: V,
+    key4: Float,
+    value4: V,
+): FloatObjectMap<V> = MutableFloatObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [FloatObjectMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun <V> floatObjectMapOf(
+    key1: Float,
+    value1: V,
+    key2: Float,
+    value2: V,
+    key3: Float,
+    value3: V,
+    key4: Float,
+    value4: V,
+    key5: Float,
+    value5: V,
+): FloatObjectMap<V> = MutableFloatObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -72,18 +142,87 @@
 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.
+ * Returns a new [MutableFloatObjectMap] with [key1] associated with [value1].
  */
-public fun <V> mutableFloatObjectMapOf(vararg pairs: Pair<Float, V>): MutableFloatObjectMap<V> =
-    MutableFloatObjectMap<V>(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun <V> mutableFloatObjectMapOf(
+    key1: Float,
+    value1: V
+): MutableFloatObjectMap<V> = MutableFloatObjectMap<V>().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [MutableFloatObjectMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun <V> mutableFloatObjectMapOf(
+    key1: Float,
+    value1: V,
+    key2: Float,
+    value2: V,
+): MutableFloatObjectMap<V> = MutableFloatObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [MutableFloatObjectMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun <V> mutableFloatObjectMapOf(
+    key1: Float,
+    value1: V,
+    key2: Float,
+    value2: V,
+    key3: Float,
+    value3: V,
+): MutableFloatObjectMap<V> = MutableFloatObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [MutableFloatObjectMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun <V> mutableFloatObjectMapOf(
+    key1: Float,
+    value1: V,
+    key2: Float,
+    value2: V,
+    key3: Float,
+    value3: V,
+    key4: Float,
+    value4: V,
+): MutableFloatObjectMap<V> = MutableFloatObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [MutableFloatObjectMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun <V> mutableFloatObjectMapOf(
+    key1: Float,
+    value1: V,
+    key2: Float,
+    value2: V,
+    key3: Float,
+    value3: V,
+    key4: Float,
+    value4: V,
+    key5: Float,
+    value5: V,
+): MutableFloatObjectMap<V> = MutableFloatObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -334,6 +473,73 @@
     }
 
     /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@FloatObjectMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(key)
+            append('=')
+            append(value)
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. Each entry is created with [transform].
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (key: Float, value: V) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@FloatObjectMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(key, value))
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
      * Returns the hash code value for this map. The hash code the sum of the hash
      * codes of each key/value pair.
      */
@@ -554,20 +760,6 @@
     }
 
     /**
-     * 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>) {
@@ -577,29 +769,6 @@
     }
 
     /**
-     * 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)
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatSet.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatSet.kt
index f08a176..321d0d2 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatSet.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatSet.kt
@@ -28,6 +28,7 @@
 
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -321,6 +322,71 @@
     public operator fun contains(element: Float): Boolean = findElementIndex(element) >= 0
 
     /**
+     * Creates a String from the elements separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@FloatSet.forEach { element ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(element)
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the elements separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. [transform] dictates how each element will be represented.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (Float) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@FloatSet.forEach { element ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(element))
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
      * 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.
      */
@@ -366,23 +432,7 @@
      * 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()
-    }
+    override fun toString(): String = joinToString(prefix = "[", postfix = "]")
 
     /**
      * Scans the set to find the index in the backing arrays of the
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntFloatMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntFloatMap.kt
index b9151ff7..8d14611 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/IntFloatMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntFloatMap.kt
@@ -22,6 +22,7 @@
 package androidx.collection
 
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -41,7 +42,6 @@
 /**
  * Returns an empty, read-only [IntFloatMap].
  */
-@Suppress("UNCHECKED_CAST")
 public fun emptyIntFloatMap(): IntFloatMap = EmptyIntFloatMap
 
 /**
@@ -50,18 +50,87 @@
 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.
+ * Returns a new [IntFloatMap] with [key1] associated with [value1].
  */
-public fun intFloatMapOf(vararg pairs: Pair<Int, Float>): IntFloatMap =
-    MutableIntFloatMap(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun intFloatMapOf(
+    key1: Int,
+    value1: Float
+): IntFloatMap = MutableIntFloatMap().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [IntFloatMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun intFloatMapOf(
+    key1: Int,
+    value1: Float,
+    key2: Int,
+    value2: Float,
+): IntFloatMap = MutableIntFloatMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [IntFloatMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun intFloatMapOf(
+    key1: Int,
+    value1: Float,
+    key2: Int,
+    value2: Float,
+    key3: Int,
+    value3: Float,
+): IntFloatMap = MutableIntFloatMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [IntFloatMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun intFloatMapOf(
+    key1: Int,
+    value1: Float,
+    key2: Int,
+    value2: Float,
+    key3: Int,
+    value3: Float,
+    key4: Int,
+    value4: Float,
+): IntFloatMap = MutableIntFloatMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [IntFloatMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun intFloatMapOf(
+    key1: Int,
+    value1: Float,
+    key2: Int,
+    value2: Float,
+    key3: Int,
+    value3: Float,
+    key4: Int,
+    value4: Float,
+    key5: Int,
+    value5: Float,
+): IntFloatMap = MutableIntFloatMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -70,18 +139,87 @@
 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.
+ * Returns a new [MutableIntFloatMap] with [key1] associated with [value1].
  */
-public fun mutableIntFloatMapOf(vararg pairs: Pair<Int, Float>): MutableIntFloatMap =
-    MutableIntFloatMap(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun mutableIntFloatMapOf(
+    key1: Int,
+    value1: Float
+): MutableIntFloatMap = MutableIntFloatMap().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [MutableIntFloatMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun mutableIntFloatMapOf(
+    key1: Int,
+    value1: Float,
+    key2: Int,
+    value2: Float,
+): MutableIntFloatMap = MutableIntFloatMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [MutableIntFloatMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun mutableIntFloatMapOf(
+    key1: Int,
+    value1: Float,
+    key2: Int,
+    value2: Float,
+    key3: Int,
+    value3: Float,
+): MutableIntFloatMap = MutableIntFloatMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [MutableIntFloatMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun mutableIntFloatMapOf(
+    key1: Int,
+    value1: Float,
+    key2: Int,
+    value2: Float,
+    key3: Int,
+    value3: Float,
+    key4: Int,
+    value4: Float,
+): MutableIntFloatMap = MutableIntFloatMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [MutableIntFloatMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun mutableIntFloatMapOf(
+    key1: Int,
+    value1: Float,
+    key2: Int,
+    value2: Float,
+    key3: Int,
+    value3: Float,
+    key4: Int,
+    value4: Float,
+    key5: Int,
+    value5: Float,
+): MutableIntFloatMap = MutableIntFloatMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -336,6 +474,73 @@
     }
 
     /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@IntFloatMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(key)
+            append('=')
+            append(value)
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. Each entry is created with [transform].
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (key: Int, value: Float) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@IntFloatMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(key, value))
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
      * Returns the hash code value for this map. The hash code the sum of the hash
      * codes of each key/value pair.
      */
@@ -554,20 +759,6 @@
     }
 
     /**
-     * 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) {
@@ -577,29 +768,6 @@
     }
 
     /**
-     * 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)
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntIntMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntIntMap.kt
index 5d288b2..618fc54 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/IntIntMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntIntMap.kt
@@ -22,6 +22,7 @@
 package androidx.collection
 
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -41,7 +42,6 @@
 /**
  * Returns an empty, read-only [IntIntMap].
  */
-@Suppress("UNCHECKED_CAST")
 public fun emptyIntIntMap(): IntIntMap = EmptyIntIntMap
 
 /**
@@ -50,18 +50,87 @@
 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.
+ * Returns a new [IntIntMap] with [key1] associated with [value1].
  */
-public fun intIntMapOf(vararg pairs: Pair<Int, Int>): IntIntMap =
-    MutableIntIntMap(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun intIntMapOf(
+    key1: Int,
+    value1: Int
+): IntIntMap = MutableIntIntMap().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [IntIntMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun intIntMapOf(
+    key1: Int,
+    value1: Int,
+    key2: Int,
+    value2: Int,
+): IntIntMap = MutableIntIntMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [IntIntMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun intIntMapOf(
+    key1: Int,
+    value1: Int,
+    key2: Int,
+    value2: Int,
+    key3: Int,
+    value3: Int,
+): IntIntMap = MutableIntIntMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [IntIntMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun intIntMapOf(
+    key1: Int,
+    value1: Int,
+    key2: Int,
+    value2: Int,
+    key3: Int,
+    value3: Int,
+    key4: Int,
+    value4: Int,
+): IntIntMap = MutableIntIntMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [IntIntMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun intIntMapOf(
+    key1: Int,
+    value1: Int,
+    key2: Int,
+    value2: Int,
+    key3: Int,
+    value3: Int,
+    key4: Int,
+    value4: Int,
+    key5: Int,
+    value5: Int,
+): IntIntMap = MutableIntIntMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -70,18 +139,87 @@
 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.
+ * Returns a new [MutableIntIntMap] with [key1] associated with [value1].
  */
-public fun mutableIntIntMapOf(vararg pairs: Pair<Int, Int>): MutableIntIntMap =
-    MutableIntIntMap(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun mutableIntIntMapOf(
+    key1: Int,
+    value1: Int
+): MutableIntIntMap = MutableIntIntMap().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [MutableIntIntMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun mutableIntIntMapOf(
+    key1: Int,
+    value1: Int,
+    key2: Int,
+    value2: Int,
+): MutableIntIntMap = MutableIntIntMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [MutableIntIntMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun mutableIntIntMapOf(
+    key1: Int,
+    value1: Int,
+    key2: Int,
+    value2: Int,
+    key3: Int,
+    value3: Int,
+): MutableIntIntMap = MutableIntIntMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [MutableIntIntMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun mutableIntIntMapOf(
+    key1: Int,
+    value1: Int,
+    key2: Int,
+    value2: Int,
+    key3: Int,
+    value3: Int,
+    key4: Int,
+    value4: Int,
+): MutableIntIntMap = MutableIntIntMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [MutableIntIntMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun mutableIntIntMapOf(
+    key1: Int,
+    value1: Int,
+    key2: Int,
+    value2: Int,
+    key3: Int,
+    value3: Int,
+    key4: Int,
+    value4: Int,
+    key5: Int,
+    value5: Int,
+): MutableIntIntMap = MutableIntIntMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -336,6 +474,73 @@
     }
 
     /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@IntIntMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(key)
+            append('=')
+            append(value)
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. Each entry is created with [transform].
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (key: Int, value: Int) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@IntIntMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(key, value))
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
      * Returns the hash code value for this map. The hash code the sum of the hash
      * codes of each key/value pair.
      */
@@ -554,20 +759,6 @@
     }
 
     /**
-     * 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) {
@@ -577,29 +768,6 @@
     }
 
     /**
-     * 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)
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/PairIntInt.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntIntPair.kt
similarity index 94%
rename from collection/collection/src/commonMain/kotlin/androidx/collection/PairIntInt.kt
rename to collection/collection/src/commonMain/kotlin/androidx/collection/IntIntPair.kt
index 0c7df8f..f192d1e 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/PairIntInt.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntIntPair.kt
@@ -26,14 +26,14 @@
  * *Note*: This class is optimized by using a value class, a Kotlin language featured
  * not available from Java code. Java developers can get the same functionality by
  * using [Pair] or by constructing a custom implementation using Int parameters
- * directly (see [PairLongLong] for an example).
+ * directly (see [LongLongPair] for an example).
  */
 @JvmInline
-public value class PairIntInt internal constructor(
+public value class IntIntPair internal constructor(
     @PublishedApi @JvmField internal val packedValue: Long
 ) {
     /**
-     * Constructs a [PairIntInt] with two [Int] values.
+     * Constructs a [IntIntPair] with two [Int] values.
      *
      * @param first the first value in the pair
      * @param second the second value in the pair
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntList.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntList.kt
index 4260def..0709d1c 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/IntList.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntList.kt
@@ -21,6 +21,7 @@
 import kotlin.contracts.ExperimentalContracts
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -425,6 +426,67 @@
     }
 
     /**
+     * Creates a String from the elements separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        this@IntList.forEachIndexed { index, element ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(element)
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the elements separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. [transform] dictates how each element will be represented.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (Int) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        this@IntList.forEachIndexed { index, element ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(element))
+        }
+        append(postfix)
+    }
+
+    /**
      * Returns a hash code based on the contents of the [IntList].
      */
     override fun hashCode(): Int {
@@ -457,23 +519,7 @@
      * 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(']')
-        }
-    }
+    override fun toString(): String = joinToString(prefix = "[", postfix = "]")
 }
 
 /**
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntLongMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntLongMap.kt
index ce4455a..ccf0d9e 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/IntLongMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntLongMap.kt
@@ -22,6 +22,7 @@
 package androidx.collection
 
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -41,7 +42,6 @@
 /**
  * Returns an empty, read-only [IntLongMap].
  */
-@Suppress("UNCHECKED_CAST")
 public fun emptyIntLongMap(): IntLongMap = EmptyIntLongMap
 
 /**
@@ -50,18 +50,87 @@
 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.
+ * Returns a new [IntLongMap] with [key1] associated with [value1].
  */
-public fun intLongMapOf(vararg pairs: Pair<Int, Long>): IntLongMap =
-    MutableIntLongMap(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun intLongMapOf(
+    key1: Int,
+    value1: Long
+): IntLongMap = MutableIntLongMap().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [IntLongMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun intLongMapOf(
+    key1: Int,
+    value1: Long,
+    key2: Int,
+    value2: Long,
+): IntLongMap = MutableIntLongMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [IntLongMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun intLongMapOf(
+    key1: Int,
+    value1: Long,
+    key2: Int,
+    value2: Long,
+    key3: Int,
+    value3: Long,
+): IntLongMap = MutableIntLongMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [IntLongMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun intLongMapOf(
+    key1: Int,
+    value1: Long,
+    key2: Int,
+    value2: Long,
+    key3: Int,
+    value3: Long,
+    key4: Int,
+    value4: Long,
+): IntLongMap = MutableIntLongMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [IntLongMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun intLongMapOf(
+    key1: Int,
+    value1: Long,
+    key2: Int,
+    value2: Long,
+    key3: Int,
+    value3: Long,
+    key4: Int,
+    value4: Long,
+    key5: Int,
+    value5: Long,
+): IntLongMap = MutableIntLongMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -70,18 +139,87 @@
 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.
+ * Returns a new [MutableIntLongMap] with [key1] associated with [value1].
  */
-public fun mutableIntLongMapOf(vararg pairs: Pair<Int, Long>): MutableIntLongMap =
-    MutableIntLongMap(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun mutableIntLongMapOf(
+    key1: Int,
+    value1: Long
+): MutableIntLongMap = MutableIntLongMap().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [MutableIntLongMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun mutableIntLongMapOf(
+    key1: Int,
+    value1: Long,
+    key2: Int,
+    value2: Long,
+): MutableIntLongMap = MutableIntLongMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [MutableIntLongMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun mutableIntLongMapOf(
+    key1: Int,
+    value1: Long,
+    key2: Int,
+    value2: Long,
+    key3: Int,
+    value3: Long,
+): MutableIntLongMap = MutableIntLongMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [MutableIntLongMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun mutableIntLongMapOf(
+    key1: Int,
+    value1: Long,
+    key2: Int,
+    value2: Long,
+    key3: Int,
+    value3: Long,
+    key4: Int,
+    value4: Long,
+): MutableIntLongMap = MutableIntLongMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [MutableIntLongMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun mutableIntLongMapOf(
+    key1: Int,
+    value1: Long,
+    key2: Int,
+    value2: Long,
+    key3: Int,
+    value3: Long,
+    key4: Int,
+    value4: Long,
+    key5: Int,
+    value5: Long,
+): MutableIntLongMap = MutableIntLongMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -336,6 +474,73 @@
     }
 
     /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@IntLongMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(key)
+            append('=')
+            append(value)
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. Each entry is created with [transform].
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (key: Int, value: Long) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@IntLongMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(key, value))
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
      * Returns the hash code value for this map. The hash code the sum of the hash
      * codes of each key/value pair.
      */
@@ -554,20 +759,6 @@
     }
 
     /**
-     * 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) {
@@ -577,29 +768,6 @@
     }
 
     /**
-     * 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)
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntObjectMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntObjectMap.kt
index 1626d89..0f101b1 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/IntObjectMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntObjectMap.kt
@@ -23,6 +23,7 @@
 
 import androidx.collection.internal.EMPTY_OBJECTS
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -52,18 +53,87 @@
 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.
+ * Returns a new [IntObjectMap] with [key1] associated with [value1].
  */
-public fun <V> intObjectMapOf(vararg pairs: Pair<Int, V>): IntObjectMap<V> =
-    MutableIntObjectMap<V>(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun <V> intObjectMapOf(
+    key1: Int,
+    value1: V
+): IntObjectMap<V> = MutableIntObjectMap<V>().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [IntObjectMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun <V> intObjectMapOf(
+    key1: Int,
+    value1: V,
+    key2: Int,
+    value2: V,
+): IntObjectMap<V> = MutableIntObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [IntObjectMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun <V> intObjectMapOf(
+    key1: Int,
+    value1: V,
+    key2: Int,
+    value2: V,
+    key3: Int,
+    value3: V,
+): IntObjectMap<V> = MutableIntObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [IntObjectMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun <V> intObjectMapOf(
+    key1: Int,
+    value1: V,
+    key2: Int,
+    value2: V,
+    key3: Int,
+    value3: V,
+    key4: Int,
+    value4: V,
+): IntObjectMap<V> = MutableIntObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [IntObjectMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun <V> intObjectMapOf(
+    key1: Int,
+    value1: V,
+    key2: Int,
+    value2: V,
+    key3: Int,
+    value3: V,
+    key4: Int,
+    value4: V,
+    key5: Int,
+    value5: V,
+): IntObjectMap<V> = MutableIntObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -72,18 +142,87 @@
 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.
+ * Returns a new [MutableIntObjectMap] with [key1] associated with [value1].
  */
-public fun <V> mutableIntObjectMapOf(vararg pairs: Pair<Int, V>): MutableIntObjectMap<V> =
-    MutableIntObjectMap<V>(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun <V> mutableIntObjectMapOf(
+    key1: Int,
+    value1: V
+): MutableIntObjectMap<V> = MutableIntObjectMap<V>().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [MutableIntObjectMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun <V> mutableIntObjectMapOf(
+    key1: Int,
+    value1: V,
+    key2: Int,
+    value2: V,
+): MutableIntObjectMap<V> = MutableIntObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [MutableIntObjectMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun <V> mutableIntObjectMapOf(
+    key1: Int,
+    value1: V,
+    key2: Int,
+    value2: V,
+    key3: Int,
+    value3: V,
+): MutableIntObjectMap<V> = MutableIntObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [MutableIntObjectMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun <V> mutableIntObjectMapOf(
+    key1: Int,
+    value1: V,
+    key2: Int,
+    value2: V,
+    key3: Int,
+    value3: V,
+    key4: Int,
+    value4: V,
+): MutableIntObjectMap<V> = MutableIntObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [MutableIntObjectMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun <V> mutableIntObjectMapOf(
+    key1: Int,
+    value1: V,
+    key2: Int,
+    value2: V,
+    key3: Int,
+    value3: V,
+    key4: Int,
+    value4: V,
+    key5: Int,
+    value5: V,
+): MutableIntObjectMap<V> = MutableIntObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -334,6 +473,73 @@
     }
 
     /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@IntObjectMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(key)
+            append('=')
+            append(value)
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. Each entry is created with [transform].
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (key: Int, value: V) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@IntObjectMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(key, value))
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
      * Returns the hash code value for this map. The hash code the sum of the hash
      * codes of each key/value pair.
      */
@@ -554,20 +760,6 @@
     }
 
     /**
-     * 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>) {
@@ -577,29 +769,6 @@
     }
 
     /**
-     * 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)
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntSet.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntSet.kt
index 282c94d..1713598 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/IntSet.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntSet.kt
@@ -28,6 +28,7 @@
 
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -321,6 +322,71 @@
     public operator fun contains(element: Int): Boolean = findElementIndex(element) >= 0
 
     /**
+     * Creates a String from the elements separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@IntSet.forEach { element ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(element)
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the elements separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. [transform] dictates how each element will be represented.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (Int) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@IntSet.forEach { element ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(element))
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
      * 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.
      */
@@ -366,23 +432,7 @@
      * 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()
-    }
+    override fun toString(): String = joinToString(prefix = "[", postfix = "]")
 
     /**
      * Scans the set to find the index in the backing arrays of the
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongFloatMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongFloatMap.kt
index 797b7fb..f82d6ca 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/LongFloatMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongFloatMap.kt
@@ -22,6 +22,7 @@
 package androidx.collection
 
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -41,7 +42,6 @@
 /**
  * Returns an empty, read-only [LongFloatMap].
  */
-@Suppress("UNCHECKED_CAST")
 public fun emptyLongFloatMap(): LongFloatMap = EmptyLongFloatMap
 
 /**
@@ -50,18 +50,87 @@
 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.
+ * Returns a new [LongFloatMap] with [key1] associated with [value1].
  */
-public fun longFloatMapOf(vararg pairs: Pair<Long, Float>): LongFloatMap =
-    MutableLongFloatMap(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun longFloatMapOf(
+    key1: Long,
+    value1: Float
+): LongFloatMap = MutableLongFloatMap().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [LongFloatMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun longFloatMapOf(
+    key1: Long,
+    value1: Float,
+    key2: Long,
+    value2: Float,
+): LongFloatMap = MutableLongFloatMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [LongFloatMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun longFloatMapOf(
+    key1: Long,
+    value1: Float,
+    key2: Long,
+    value2: Float,
+    key3: Long,
+    value3: Float,
+): LongFloatMap = MutableLongFloatMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [LongFloatMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun longFloatMapOf(
+    key1: Long,
+    value1: Float,
+    key2: Long,
+    value2: Float,
+    key3: Long,
+    value3: Float,
+    key4: Long,
+    value4: Float,
+): LongFloatMap = MutableLongFloatMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [LongFloatMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun longFloatMapOf(
+    key1: Long,
+    value1: Float,
+    key2: Long,
+    value2: Float,
+    key3: Long,
+    value3: Float,
+    key4: Long,
+    value4: Float,
+    key5: Long,
+    value5: Float,
+): LongFloatMap = MutableLongFloatMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -70,18 +139,87 @@
 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.
+ * Returns a new [MutableLongFloatMap] with [key1] associated with [value1].
  */
-public fun mutableLongFloatMapOf(vararg pairs: Pair<Long, Float>): MutableLongFloatMap =
-    MutableLongFloatMap(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun mutableLongFloatMapOf(
+    key1: Long,
+    value1: Float
+): MutableLongFloatMap = MutableLongFloatMap().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [MutableLongFloatMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun mutableLongFloatMapOf(
+    key1: Long,
+    value1: Float,
+    key2: Long,
+    value2: Float,
+): MutableLongFloatMap = MutableLongFloatMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [MutableLongFloatMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun mutableLongFloatMapOf(
+    key1: Long,
+    value1: Float,
+    key2: Long,
+    value2: Float,
+    key3: Long,
+    value3: Float,
+): MutableLongFloatMap = MutableLongFloatMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [MutableLongFloatMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun mutableLongFloatMapOf(
+    key1: Long,
+    value1: Float,
+    key2: Long,
+    value2: Float,
+    key3: Long,
+    value3: Float,
+    key4: Long,
+    value4: Float,
+): MutableLongFloatMap = MutableLongFloatMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [MutableLongFloatMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun mutableLongFloatMapOf(
+    key1: Long,
+    value1: Float,
+    key2: Long,
+    value2: Float,
+    key3: Long,
+    value3: Float,
+    key4: Long,
+    value4: Float,
+    key5: Long,
+    value5: Float,
+): MutableLongFloatMap = MutableLongFloatMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -336,6 +474,73 @@
     }
 
     /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@LongFloatMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(key)
+            append('=')
+            append(value)
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. Each entry is created with [transform].
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (key: Long, value: Float) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@LongFloatMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(key, value))
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
      * Returns the hash code value for this map. The hash code the sum of the hash
      * codes of each key/value pair.
      */
@@ -554,20 +759,6 @@
     }
 
     /**
-     * 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) {
@@ -577,29 +768,6 @@
     }
 
     /**
-     * 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)
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongIntMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongIntMap.kt
index c9c5ef6..358ec9a 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/LongIntMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongIntMap.kt
@@ -22,6 +22,7 @@
 package androidx.collection
 
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -41,7 +42,6 @@
 /**
  * Returns an empty, read-only [LongIntMap].
  */
-@Suppress("UNCHECKED_CAST")
 public fun emptyLongIntMap(): LongIntMap = EmptyLongIntMap
 
 /**
@@ -50,18 +50,87 @@
 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.
+ * Returns a new [LongIntMap] with [key1] associated with [value1].
  */
-public fun longIntMapOf(vararg pairs: Pair<Long, Int>): LongIntMap =
-    MutableLongIntMap(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun longIntMapOf(
+    key1: Long,
+    value1: Int
+): LongIntMap = MutableLongIntMap().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [LongIntMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun longIntMapOf(
+    key1: Long,
+    value1: Int,
+    key2: Long,
+    value2: Int,
+): LongIntMap = MutableLongIntMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [LongIntMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun longIntMapOf(
+    key1: Long,
+    value1: Int,
+    key2: Long,
+    value2: Int,
+    key3: Long,
+    value3: Int,
+): LongIntMap = MutableLongIntMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [LongIntMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun longIntMapOf(
+    key1: Long,
+    value1: Int,
+    key2: Long,
+    value2: Int,
+    key3: Long,
+    value3: Int,
+    key4: Long,
+    value4: Int,
+): LongIntMap = MutableLongIntMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [LongIntMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun longIntMapOf(
+    key1: Long,
+    value1: Int,
+    key2: Long,
+    value2: Int,
+    key3: Long,
+    value3: Int,
+    key4: Long,
+    value4: Int,
+    key5: Long,
+    value5: Int,
+): LongIntMap = MutableLongIntMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -70,18 +139,87 @@
 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.
+ * Returns a new [MutableLongIntMap] with [key1] associated with [value1].
  */
-public fun mutableLongIntMapOf(vararg pairs: Pair<Long, Int>): MutableLongIntMap =
-    MutableLongIntMap(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun mutableLongIntMapOf(
+    key1: Long,
+    value1: Int
+): MutableLongIntMap = MutableLongIntMap().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [MutableLongIntMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun mutableLongIntMapOf(
+    key1: Long,
+    value1: Int,
+    key2: Long,
+    value2: Int,
+): MutableLongIntMap = MutableLongIntMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [MutableLongIntMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun mutableLongIntMapOf(
+    key1: Long,
+    value1: Int,
+    key2: Long,
+    value2: Int,
+    key3: Long,
+    value3: Int,
+): MutableLongIntMap = MutableLongIntMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [MutableLongIntMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun mutableLongIntMapOf(
+    key1: Long,
+    value1: Int,
+    key2: Long,
+    value2: Int,
+    key3: Long,
+    value3: Int,
+    key4: Long,
+    value4: Int,
+): MutableLongIntMap = MutableLongIntMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [MutableLongIntMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun mutableLongIntMapOf(
+    key1: Long,
+    value1: Int,
+    key2: Long,
+    value2: Int,
+    key3: Long,
+    value3: Int,
+    key4: Long,
+    value4: Int,
+    key5: Long,
+    value5: Int,
+): MutableLongIntMap = MutableLongIntMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -336,6 +474,73 @@
     }
 
     /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@LongIntMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(key)
+            append('=')
+            append(value)
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. Each entry is created with [transform].
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (key: Long, value: Int) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@LongIntMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(key, value))
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
      * Returns the hash code value for this map. The hash code the sum of the hash
      * codes of each key/value pair.
      */
@@ -554,20 +759,6 @@
     }
 
     /**
-     * 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) {
@@ -577,29 +768,6 @@
     }
 
     /**
-     * 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)
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongList.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongList.kt
index 4f40b5b..56e15d1 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/LongList.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongList.kt
@@ -21,6 +21,7 @@
 import kotlin.contracts.ExperimentalContracts
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -425,6 +426,67 @@
     }
 
     /**
+     * Creates a String from the elements separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        this@LongList.forEachIndexed { index, element ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(element)
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the elements separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. [transform] dictates how each element will be represented.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (Long) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        this@LongList.forEachIndexed { index, element ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(element))
+        }
+        append(postfix)
+    }
+
+    /**
      * Returns a hash code based on the contents of the [LongList].
      */
     override fun hashCode(): Int {
@@ -457,23 +519,7 @@
      * 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(']')
-        }
-    }
+    override fun toString(): String = joinToString(prefix = "[", postfix = "]")
 }
 
 /**
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongLongMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongLongMap.kt
index e61a7a0..59b4f85 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/LongLongMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongLongMap.kt
@@ -22,6 +22,7 @@
 package androidx.collection
 
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -41,7 +42,6 @@
 /**
  * Returns an empty, read-only [LongLongMap].
  */
-@Suppress("UNCHECKED_CAST")
 public fun emptyLongLongMap(): LongLongMap = EmptyLongLongMap
 
 /**
@@ -50,18 +50,87 @@
 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.
+ * Returns a new [LongLongMap] with [key1] associated with [value1].
  */
-public fun longLongMapOf(vararg pairs: Pair<Long, Long>): LongLongMap =
-    MutableLongLongMap(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun longLongMapOf(
+    key1: Long,
+    value1: Long
+): LongLongMap = MutableLongLongMap().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [LongLongMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun longLongMapOf(
+    key1: Long,
+    value1: Long,
+    key2: Long,
+    value2: Long,
+): LongLongMap = MutableLongLongMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [LongLongMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun longLongMapOf(
+    key1: Long,
+    value1: Long,
+    key2: Long,
+    value2: Long,
+    key3: Long,
+    value3: Long,
+): LongLongMap = MutableLongLongMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [LongLongMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun longLongMapOf(
+    key1: Long,
+    value1: Long,
+    key2: Long,
+    value2: Long,
+    key3: Long,
+    value3: Long,
+    key4: Long,
+    value4: Long,
+): LongLongMap = MutableLongLongMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [LongLongMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun longLongMapOf(
+    key1: Long,
+    value1: Long,
+    key2: Long,
+    value2: Long,
+    key3: Long,
+    value3: Long,
+    key4: Long,
+    value4: Long,
+    key5: Long,
+    value5: Long,
+): LongLongMap = MutableLongLongMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -70,18 +139,87 @@
 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.
+ * Returns a new [MutableLongLongMap] with [key1] associated with [value1].
  */
-public fun mutableLongLongMapOf(vararg pairs: Pair<Long, Long>): MutableLongLongMap =
-    MutableLongLongMap(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun mutableLongLongMapOf(
+    key1: Long,
+    value1: Long
+): MutableLongLongMap = MutableLongLongMap().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [MutableLongLongMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun mutableLongLongMapOf(
+    key1: Long,
+    value1: Long,
+    key2: Long,
+    value2: Long,
+): MutableLongLongMap = MutableLongLongMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [MutableLongLongMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun mutableLongLongMapOf(
+    key1: Long,
+    value1: Long,
+    key2: Long,
+    value2: Long,
+    key3: Long,
+    value3: Long,
+): MutableLongLongMap = MutableLongLongMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [MutableLongLongMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun mutableLongLongMapOf(
+    key1: Long,
+    value1: Long,
+    key2: Long,
+    value2: Long,
+    key3: Long,
+    value3: Long,
+    key4: Long,
+    value4: Long,
+): MutableLongLongMap = MutableLongLongMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [MutableLongLongMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun mutableLongLongMapOf(
+    key1: Long,
+    value1: Long,
+    key2: Long,
+    value2: Long,
+    key3: Long,
+    value3: Long,
+    key4: Long,
+    value4: Long,
+    key5: Long,
+    value5: Long,
+): MutableLongLongMap = MutableLongLongMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -336,6 +474,73 @@
     }
 
     /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@LongLongMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(key)
+            append('=')
+            append(value)
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. Each entry is created with [transform].
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (key: Long, value: Long) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@LongLongMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(key, value))
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
      * Returns the hash code value for this map. The hash code the sum of the hash
      * codes of each key/value pair.
      */
@@ -554,20 +759,6 @@
     }
 
     /**
-     * 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) {
@@ -577,29 +768,6 @@
     }
 
     /**
-     * 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)
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/PairLongLong.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongLongPair.kt
similarity index 87%
rename from collection/collection/src/commonMain/kotlin/androidx/collection/PairLongLong.kt
rename to collection/collection/src/commonMain/kotlin/androidx/collection/LongLongPair.kt
index 7184f9a..ce84157 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/PairLongLong.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongLongPair.kt
@@ -23,7 +23,7 @@
  * @param first the first value in the pair
  * @param second the second value in the pair
  */
-public class PairLongLong public constructor(public val first: Long, public val second: Long) {
+public class LongLongPair public constructor(public val first: Long, public val second: Long) {
     /**
      * Returns the [first] component of the pair. For instance, the first component
      * of `PairLongLong(3, 4)` is `3`.
@@ -51,11 +51,11 @@
     /**
      * Checks the two values for equality.
      *
-     * @param other the [PairLongLong] to which this one is to be checked for equality
-     * @return true if the underlying values of the [PairLongLong] are both considered equal
+     * @param other the [LongLongPair] to which this one is to be checked for equality
+     * @return true if the underlying values of the [LongLongPair] are both considered equal
      */
     override fun equals(other: Any?): Boolean {
-        if (!(other is PairLongLong)) {
+        if (!(other is LongLongPair)) {
             return false
         }
         return other.first == first && other.second == second
@@ -64,7 +64,7 @@
     /**
      * Compute a hash code using the hash codes of the underlying values
      *
-     * @return a hashcode of the [PairLongLong]
+     * @return a hashcode of the [LongLongPair]
      */
     override fun hashCode(): Int {
         return first.hashCode() xor second.hashCode()
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongObjectMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongObjectMap.kt
index f01c754..64f367e 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/LongObjectMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongObjectMap.kt
@@ -23,6 +23,7 @@
 
 import androidx.collection.internal.EMPTY_OBJECTS
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -52,18 +53,87 @@
 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.
+ * Returns a new [LongObjectMap] with [key1] associated with [value1].
  */
-public fun <V> longObjectMapOf(vararg pairs: Pair<Long, V>): LongObjectMap<V> =
-    MutableLongObjectMap<V>(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun <V> longObjectMapOf(
+    key1: Long,
+    value1: V
+): LongObjectMap<V> = MutableLongObjectMap<V>().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [LongObjectMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun <V> longObjectMapOf(
+    key1: Long,
+    value1: V,
+    key2: Long,
+    value2: V,
+): LongObjectMap<V> = MutableLongObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [LongObjectMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun <V> longObjectMapOf(
+    key1: Long,
+    value1: V,
+    key2: Long,
+    value2: V,
+    key3: Long,
+    value3: V,
+): LongObjectMap<V> = MutableLongObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [LongObjectMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun <V> longObjectMapOf(
+    key1: Long,
+    value1: V,
+    key2: Long,
+    value2: V,
+    key3: Long,
+    value3: V,
+    key4: Long,
+    value4: V,
+): LongObjectMap<V> = MutableLongObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [LongObjectMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun <V> longObjectMapOf(
+    key1: Long,
+    value1: V,
+    key2: Long,
+    value2: V,
+    key3: Long,
+    value3: V,
+    key4: Long,
+    value4: V,
+    key5: Long,
+    value5: V,
+): LongObjectMap<V> = MutableLongObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -72,18 +142,87 @@
 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.
+ * Returns a new [MutableLongObjectMap] with [key1] associated with [value1].
  */
-public fun <V> mutableLongObjectMapOf(vararg pairs: Pair<Long, V>): MutableLongObjectMap<V> =
-    MutableLongObjectMap<V>(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun <V> mutableLongObjectMapOf(
+    key1: Long,
+    value1: V
+): MutableLongObjectMap<V> = MutableLongObjectMap<V>().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [MutableLongObjectMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun <V> mutableLongObjectMapOf(
+    key1: Long,
+    value1: V,
+    key2: Long,
+    value2: V,
+): MutableLongObjectMap<V> = MutableLongObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [MutableLongObjectMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun <V> mutableLongObjectMapOf(
+    key1: Long,
+    value1: V,
+    key2: Long,
+    value2: V,
+    key3: Long,
+    value3: V,
+): MutableLongObjectMap<V> = MutableLongObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [MutableLongObjectMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun <V> mutableLongObjectMapOf(
+    key1: Long,
+    value1: V,
+    key2: Long,
+    value2: V,
+    key3: Long,
+    value3: V,
+    key4: Long,
+    value4: V,
+): MutableLongObjectMap<V> = MutableLongObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [MutableLongObjectMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun <V> mutableLongObjectMapOf(
+    key1: Long,
+    value1: V,
+    key2: Long,
+    value2: V,
+    key3: Long,
+    value3: V,
+    key4: Long,
+    value4: V,
+    key5: Long,
+    value5: V,
+): MutableLongObjectMap<V> = MutableLongObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -334,6 +473,73 @@
     }
 
     /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@LongObjectMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(key)
+            append('=')
+            append(value)
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. Each entry is created with [transform].
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (key: Long, value: V) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@LongObjectMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(key, value))
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
      * Returns the hash code value for this map. The hash code the sum of the hash
      * codes of each key/value pair.
      */
@@ -554,20 +760,6 @@
     }
 
     /**
-     * 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>) {
@@ -577,29 +769,6 @@
     }
 
     /**
-     * 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)
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongSet.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongSet.kt
index 45734c8..3216858 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/LongSet.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongSet.kt
@@ -28,6 +28,7 @@
 
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -321,6 +322,71 @@
     public operator fun contains(element: Long): Boolean = findElementIndex(element) >= 0
 
     /**
+     * Creates a String from the elements separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@LongSet.forEach { element ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(element)
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the elements separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. [transform] dictates how each element will be represented.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (Long) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@LongSet.forEach { element ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(element))
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
      * 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.
      */
@@ -366,23 +432,7 @@
      * 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()
-    }
+    override fun toString(): String = joinToString(prefix = "[", postfix = "]")
 
     /**
      * Scans the set to find the index in the backing arrays of the
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectFloatMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectFloatMap.kt
index 95c1a3d..4b0163a 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectFloatMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectFloatMap.kt
@@ -23,6 +23,7 @@
 
 import androidx.collection.internal.EMPTY_OBJECTS
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -54,38 +55,186 @@
     EmptyObjectFloatMap as ObjectFloatMap<K>
 
 /**
- * Returns a new [MutableObjectFloatMap].
+ * Returns a new [ObjectFloatMap] with only [key1] associated with [value1].
+ */
+public fun <K> objectFloatMapOf(
+    key1: K,
+    value1: Float
+): ObjectFloatMap<K> =
+    MutableObjectFloatMap<K>().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [ObjectFloatMap] with only [key1] and [key2] associated with
+ * [value1] and [value2], respectively.
+ */
+public fun <K> objectFloatMapOf(
+    key1: K,
+    value1: Float,
+    key2: K,
+    value2: Float,
+): ObjectFloatMap<K> =
+    MutableObjectFloatMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [ObjectFloatMap] with only [key1], [key2], and [key3] associated with
+ * [value1], [value2], and [value3], respectively.
+ */
+public fun <K> objectFloatMapOf(
+    key1: K,
+    value1: Float,
+    key2: K,
+    value2: Float,
+    key3: K,
+    value3: Float,
+): ObjectFloatMap<K> =
+    MutableObjectFloatMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [ObjectFloatMap] with only [key1], [key2], [key3], and [key4] associated with
+ * [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun <K> objectFloatMapOf(
+    key1: K,
+    value1: Float,
+    key2: K,
+    value2: Float,
+    key3: K,
+    value3: Float,
+    key4: K,
+    value4: Float,
+): ObjectFloatMap<K> =
+    MutableObjectFloatMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [ObjectFloatMap] with only [key1], [key2], [key3], [key4], and [key5] associated
+ * with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun <K> objectFloatMapOf(
+    key1: K,
+    value1: Float,
+    key2: K,
+    value2: Float,
+    key3: K,
+    value3: Float,
+    key4: K,
+    value4: Float,
+    key5: K,
+    value5: Float,
+): ObjectFloatMap<K> =
+    MutableObjectFloatMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
+    }
+
+/**
+ * Returns a new empty [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.
+ * Returns a new [MutableObjectFloatMap] with only [key1] associated with [value1].
  */
-public fun <K> objectFloatMapOf(vararg pairs: Pair<K, Float>): ObjectFloatMap<K> =
-    MutableObjectFloatMap<K>(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun <K> mutableObjectFloatMapOf(
+    key1: K,
+    value1: Float,
+): MutableObjectFloatMap<K> =
+    MutableObjectFloatMap<K>().also { map ->
+        map[key1] = value1
     }
 
 /**
- * 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.
+ * Returns a new [MutableObjectFloatMap] with only [key1] and [key2] associated with
+ * [value1] and [value2], respectively.
  */
-public fun <K> mutableObjectFloatMapOf(vararg pairs: Pair<K, Float>): MutableObjectFloatMap<K> =
-    MutableObjectFloatMap<K>(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun <K> mutableObjectFloatMapOf(
+    key1: K,
+    value1: Float,
+    key2: K,
+    value2: Float,
+): MutableObjectFloatMap<K> =
+    MutableObjectFloatMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [MutableObjectFloatMap] with only [key1], [key2], and [key3] associated with
+ * [value1], [value2], and [value3], respectively.
+ */
+public fun <K> mutableObjectFloatMapOf(
+    key1: K,
+    value1: Float,
+    key2: K,
+    value2: Float,
+    key3: K,
+    value3: Float,
+): MutableObjectFloatMap<K> =
+    MutableObjectFloatMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [MutableObjectFloatMap] with only [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun <K> mutableObjectFloatMapOf(
+    key1: K,
+    value1: Float,
+    key2: K,
+    value2: Float,
+    key3: K,
+    value3: Float,
+    key4: K,
+    value4: Float,
+): MutableObjectFloatMap<K> =
+    MutableObjectFloatMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [MutableObjectFloatMap] with only [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun <K> mutableObjectFloatMapOf(
+    key1: K,
+    value1: Float,
+    key2: K,
+    value2: Float,
+    key3: K,
+    value3: Float,
+    key4: K,
+    value4: Float,
+    key5: K,
+    value5: Float,
+): MutableObjectFloatMap<K> =
+    MutableObjectFloatMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -343,6 +492,73 @@
     }
 
     /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@ObjectFloatMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(key)
+            append('=')
+            append(value)
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. Each entry is created with [transform].
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (key: K, value: Float) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@ObjectFloatMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(key, value))
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
      * Returns the hash code value for this map. The hash code the sum of the hash
      * codes of each key/value pair.
      */
@@ -560,20 +776,6 @@
     }
 
     /**
-     * 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>) {
@@ -583,29 +785,6 @@
     }
 
     /**
-     * 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)
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectIntMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectIntMap.kt
index 697f1b0..91b2ee3 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectIntMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectIntMap.kt
@@ -23,6 +23,7 @@
 
 import androidx.collection.internal.EMPTY_OBJECTS
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -54,38 +55,186 @@
     EmptyObjectIntMap as ObjectIntMap<K>
 
 /**
- * Returns a new [MutableObjectIntMap].
+ * Returns a new [ObjectIntMap] with only [key1] associated with [value1].
+ */
+public fun <K> objectIntMapOf(
+    key1: K,
+    value1: Int
+): ObjectIntMap<K> =
+    MutableObjectIntMap<K>().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [ObjectIntMap] with only [key1] and [key2] associated with
+ * [value1] and [value2], respectively.
+ */
+public fun <K> objectIntMapOf(
+    key1: K,
+    value1: Int,
+    key2: K,
+    value2: Int,
+): ObjectIntMap<K> =
+    MutableObjectIntMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [ObjectIntMap] with only [key1], [key2], and [key3] associated with
+ * [value1], [value2], and [value3], respectively.
+ */
+public fun <K> objectIntMapOf(
+    key1: K,
+    value1: Int,
+    key2: K,
+    value2: Int,
+    key3: K,
+    value3: Int,
+): ObjectIntMap<K> =
+    MutableObjectIntMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [ObjectIntMap] with only [key1], [key2], [key3], and [key4] associated with
+ * [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun <K> objectIntMapOf(
+    key1: K,
+    value1: Int,
+    key2: K,
+    value2: Int,
+    key3: K,
+    value3: Int,
+    key4: K,
+    value4: Int,
+): ObjectIntMap<K> =
+    MutableObjectIntMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [ObjectIntMap] with only [key1], [key2], [key3], [key4], and [key5] associated
+ * with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun <K> objectIntMapOf(
+    key1: K,
+    value1: Int,
+    key2: K,
+    value2: Int,
+    key3: K,
+    value3: Int,
+    key4: K,
+    value4: Int,
+    key5: K,
+    value5: Int,
+): ObjectIntMap<K> =
+    MutableObjectIntMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
+    }
+
+/**
+ * Returns a new empty [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.
+ * Returns a new [MutableObjectIntMap] with only [key1] associated with [value1].
  */
-public fun <K> objectIntMapOf(vararg pairs: Pair<K, Int>): ObjectIntMap<K> =
-    MutableObjectIntMap<K>(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun <K> mutableObjectIntMapOf(
+    key1: K,
+    value1: Int,
+): MutableObjectIntMap<K> =
+    MutableObjectIntMap<K>().also { map ->
+        map[key1] = value1
     }
 
 /**
- * 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.
+ * Returns a new [MutableObjectIntMap] with only [key1] and [key2] associated with
+ * [value1] and [value2], respectively.
  */
-public fun <K> mutableObjectIntMapOf(vararg pairs: Pair<K, Int>): MutableObjectIntMap<K> =
-    MutableObjectIntMap<K>(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun <K> mutableObjectIntMapOf(
+    key1: K,
+    value1: Int,
+    key2: K,
+    value2: Int,
+): MutableObjectIntMap<K> =
+    MutableObjectIntMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [MutableObjectIntMap] with only [key1], [key2], and [key3] associated with
+ * [value1], [value2], and [value3], respectively.
+ */
+public fun <K> mutableObjectIntMapOf(
+    key1: K,
+    value1: Int,
+    key2: K,
+    value2: Int,
+    key3: K,
+    value3: Int,
+): MutableObjectIntMap<K> =
+    MutableObjectIntMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [MutableObjectIntMap] with only [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun <K> mutableObjectIntMapOf(
+    key1: K,
+    value1: Int,
+    key2: K,
+    value2: Int,
+    key3: K,
+    value3: Int,
+    key4: K,
+    value4: Int,
+): MutableObjectIntMap<K> =
+    MutableObjectIntMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [MutableObjectIntMap] with only [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun <K> mutableObjectIntMapOf(
+    key1: K,
+    value1: Int,
+    key2: K,
+    value2: Int,
+    key3: K,
+    value3: Int,
+    key4: K,
+    value4: Int,
+    key5: K,
+    value5: Int,
+): MutableObjectIntMap<K> =
+    MutableObjectIntMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -343,6 +492,73 @@
     }
 
     /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@ObjectIntMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(key)
+            append('=')
+            append(value)
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. Each entry is created with [transform].
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (key: K, value: Int) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@ObjectIntMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(key, value))
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
      * Returns the hash code value for this map. The hash code the sum of the hash
      * codes of each key/value pair.
      */
@@ -560,20 +776,6 @@
     }
 
     /**
-     * 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>) {
@@ -583,29 +785,6 @@
     }
 
     /**
-     * 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)
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectList.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectList.kt
index 105ebaf..89002a95 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectList.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectList.kt
@@ -21,6 +21,7 @@
 import kotlin.contracts.ExperimentalContracts
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 /**
  * [ObjectList] is a [List]-like collection for reference types. It is optimized for fast
@@ -36,6 +37,11 @@
  * **Note** [List] access is available through [asList] when developers need access to the
  * common API.
  *
+ * It is best to use this for all internal implementations where a list of reference types
+ * is needed. Use [List] in public API to take advantage of the commonly-used interface.
+ * It is common to use [ObjectList] internally and use [asList] to get a [List] interface
+ * for interacting with public APIs.
+ *
  * @see MutableObjectList
  * @see FloatList
  * @see IntList
@@ -118,12 +124,7 @@
      * 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
+        return indexOf(element) >= 0
     }
 
     /**
@@ -391,9 +392,21 @@
      * 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
+        // Comparing with == for each element is slower than comparing with .equals().
+        // We split the iteration for null and for non-null to speed it up.
+        // See ObjectListBenchmarkTest.contains()
+        if (element == null) {
+            forEachIndexed { i, item ->
+                if (item == null) {
+                    return i
+                }
+            }
+        } else {
+            forEachIndexed { i, item ->
+                @Suppress("ReplaceCallWithBinaryOperator")
+                if (element.equals(item)) {
+                    return i
+                }
             }
         }
         return -1
@@ -489,15 +502,64 @@
      * [element] or `-1` if no elements match.
      */
     public fun lastIndexOf(element: E): Int {
-        forEachReversedIndexed { i, item ->
-            if (element == item) {
-                return i
+        // Comparing with == for each element is slower than comparing with .equals().
+        // We split the iteration for null and for non-null to speed it up.
+        // See ObjectListBenchmarkTest.contains()
+        if (element == null) {
+            forEachReversedIndexed { i, item ->
+                if (item == null) {
+                    return i
+                }
+            }
+        } else {
+            forEachReversedIndexed { i, item ->
+                @Suppress("ReplaceCallWithBinaryOperator")
+                if (element.equals(item)) {
+                    return i
+                }
             }
         }
         return -1
     }
 
     /**
+     * Creates a String from the elements separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     *
+     * [transform] may be supplied to convert each element to a custom String.
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        transform: ((E) -> CharSequence)? = null
+    ): String = buildString {
+        append(prefix)
+        this@ObjectList.forEachIndexed { index, element ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            if (transform == null) {
+                append(element)
+            } else {
+                append(transform(element))
+            }
+        }
+        append(postfix)
+    }
+
+    /**
      * 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
@@ -539,21 +601,11 @@
      * 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(']')
+    override fun toString(): String = joinToString(prefix = "[", postfix = "]") { element ->
+        if (element === this) {
+            "(this)"
+        } else {
+            element.toString()
         }
     }
 }
@@ -575,6 +627,11 @@
  * **Note** [MutableList] access is available through [asMutableList] when developers need
  * access to the common API.
  *
+ * It is best to use this for all internal implementations where a list of reference types
+ * is needed. Use [MutableList] in public API to take advantage of the commonly-used interface.
+ * It is common to use [MutableObjectList] internally and use [asMutableList] or [asList]
+ * to get a [MutableList] or [List] interface for interacting with public APIs.
+ *
  * @see ObjectList
  * @see MutableFloatList
  * @see MutableIntList
@@ -1101,7 +1158,7 @@
         val content = content
         for (i in lastIndex downTo 0) {
             val element = content[i]
-            if (elements.indexOfFirst { it == element } < 0) {
+            if (elements.indexOf(element) < 0) {
                 removeAt(i)
             }
         }
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectLongMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectLongMap.kt
index 092855b..57f6d29 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectLongMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectLongMap.kt
@@ -23,6 +23,7 @@
 
 import androidx.collection.internal.EMPTY_OBJECTS
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -54,38 +55,186 @@
     EmptyObjectLongMap as ObjectLongMap<K>
 
 /**
- * Returns a new [MutableObjectLongMap].
+ * Returns a new [ObjectLongMap] with only [key1] associated with [value1].
+ */
+public fun <K> objectLongMapOf(
+    key1: K,
+    value1: Long
+): ObjectLongMap<K> =
+    MutableObjectLongMap<K>().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [ObjectLongMap] with only [key1] and [key2] associated with
+ * [value1] and [value2], respectively.
+ */
+public fun <K> objectLongMapOf(
+    key1: K,
+    value1: Long,
+    key2: K,
+    value2: Long,
+): ObjectLongMap<K> =
+    MutableObjectLongMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [ObjectLongMap] with only [key1], [key2], and [key3] associated with
+ * [value1], [value2], and [value3], respectively.
+ */
+public fun <K> objectLongMapOf(
+    key1: K,
+    value1: Long,
+    key2: K,
+    value2: Long,
+    key3: K,
+    value3: Long,
+): ObjectLongMap<K> =
+    MutableObjectLongMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [ObjectLongMap] with only [key1], [key2], [key3], and [key4] associated with
+ * [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun <K> objectLongMapOf(
+    key1: K,
+    value1: Long,
+    key2: K,
+    value2: Long,
+    key3: K,
+    value3: Long,
+    key4: K,
+    value4: Long,
+): ObjectLongMap<K> =
+    MutableObjectLongMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [ObjectLongMap] with only [key1], [key2], [key3], [key4], and [key5] associated
+ * with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun <K> objectLongMapOf(
+    key1: K,
+    value1: Long,
+    key2: K,
+    value2: Long,
+    key3: K,
+    value3: Long,
+    key4: K,
+    value4: Long,
+    key5: K,
+    value5: Long,
+): ObjectLongMap<K> =
+    MutableObjectLongMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
+    }
+
+/**
+ * Returns a new empty [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.
+ * Returns a new [MutableObjectLongMap] with only [key1] associated with [value1].
  */
-public fun <K> objectLongMapOf(vararg pairs: Pair<K, Long>): ObjectLongMap<K> =
-    MutableObjectLongMap<K>(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun <K> mutableObjectLongMapOf(
+    key1: K,
+    value1: Long,
+): MutableObjectLongMap<K> =
+    MutableObjectLongMap<K>().also { map ->
+        map[key1] = value1
     }
 
 /**
- * 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.
+ * Returns a new [MutableObjectLongMap] with only [key1] and [key2] associated with
+ * [value1] and [value2], respectively.
  */
-public fun <K> mutableObjectLongMapOf(vararg pairs: Pair<K, Long>): MutableObjectLongMap<K> =
-    MutableObjectLongMap<K>(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun <K> mutableObjectLongMapOf(
+    key1: K,
+    value1: Long,
+    key2: K,
+    value2: Long,
+): MutableObjectLongMap<K> =
+    MutableObjectLongMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [MutableObjectLongMap] with only [key1], [key2], and [key3] associated with
+ * [value1], [value2], and [value3], respectively.
+ */
+public fun <K> mutableObjectLongMapOf(
+    key1: K,
+    value1: Long,
+    key2: K,
+    value2: Long,
+    key3: K,
+    value3: Long,
+): MutableObjectLongMap<K> =
+    MutableObjectLongMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [MutableObjectLongMap] with only [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun <K> mutableObjectLongMapOf(
+    key1: K,
+    value1: Long,
+    key2: K,
+    value2: Long,
+    key3: K,
+    value3: Long,
+    key4: K,
+    value4: Long,
+): MutableObjectLongMap<K> =
+    MutableObjectLongMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [MutableObjectLongMap] with only [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun <K> mutableObjectLongMapOf(
+    key1: K,
+    value1: Long,
+    key2: K,
+    value2: Long,
+    key3: K,
+    value3: Long,
+    key4: K,
+    value4: Long,
+    key5: K,
+    value5: Long,
+): MutableObjectLongMap<K> =
+    MutableObjectLongMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -343,6 +492,73 @@
     }
 
     /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@ObjectLongMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(key)
+            append('=')
+            append(value)
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. Each entry is created with [transform].
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (key: K, value: Long) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@ObjectLongMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(key, value))
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
      * Returns the hash code value for this map. The hash code the sum of the hash
      * codes of each key/value pair.
      */
@@ -560,20 +776,6 @@
     }
 
     /**
-     * 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>) {
@@ -583,29 +785,6 @@
     }
 
     /**
-     * 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)
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterMap.kt
index 37be5a5..547f6a3 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterMap.kt
@@ -28,6 +28,7 @@
 
 import androidx.collection.internal.EMPTY_OBJECTS
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 import kotlin.math.max
 
 // A "flat" hash map based on abseil's flat_hash_map
@@ -482,6 +483,47 @@
     }
 
     /**
+     * Creates a String from the elements separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     *
+     * [transform] may be supplied to convert each element to a custom String.
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        transform: ((key: K, value: V) -> CharSequence)? = null
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@ScatterMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            if (transform == null) {
+                append(key)
+                append('=')
+                append(value)
+            } else {
+                append(transform(key, value))
+            }
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
      * Returns the hash code value for this map. The hash code the sum of the hash
      * codes of each key/value pair.
      */
@@ -988,6 +1030,24 @@
         }
     }
 
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: ScatterSet<K>) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
+    /**
+     * Removes the specified [keys] and their associated value from the map.
+     */
+    public inline operator fun minusAssign(keys: ObjectList<K>) {
+        keys.forEach { key ->
+            remove(key)
+        }
+    }
+
     private fun removeValueAt(index: Int): V? {
         _size -= 1
 
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterSet.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterSet.kt
index fe0cfd4..93587cf 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterSet.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/ScatterSet.kt
@@ -29,6 +29,7 @@
 import androidx.collection.internal.EMPTY_OBJECTS
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // This is a copy of ScatterMap, but without values
 
@@ -328,6 +329,45 @@
     public operator fun contains(element: E): Boolean = findElementIndex(element) >= 0
 
     /**
+     * Creates a String from the elements separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     *
+     * [transform] may be supplied to convert each element to a custom String.
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        transform: ((E) -> CharSequence)? = null
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@ScatterSet.forEach { element ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            if (transform == null) {
+                append(element)
+            } else {
+                append(transform(element))
+            }
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
      * 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, where the hash code
      * of a null element is defined to be zero
@@ -377,22 +417,12 @@
      * 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 "[]"
+    override fun toString(): String = joinToString(prefix = "[", postfix = "]") { element ->
+        if (element === this) {
+            "(this)"
+        } else {
+            element.toString()
         }
-
-        val s = StringBuilder().append('[')
-        val last = _size - 1
-        var index = 0
-        forEach { element ->
-            s.append(if (element === this) "(this)" else element)
-            if (index++ < last) {
-                s.append(',').append(' ')
-            }
-        }
-
-        return s.append(']').toString()
     }
 
     /**
@@ -613,6 +643,18 @@
     }
 
     /**
+     * Adds all the elements in the [elements] set into this set.
+     * @param elements An [ObjectList] whose elements are to be added 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(elements: ObjectList<E>): 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.
      */
@@ -653,6 +695,16 @@
     }
 
     /**
+     * Adds all the elements in the [elements] set into this set.
+     * @param elements An [ObjectList] whose elements are to be added to the set
+     */
+    public operator fun plusAssign(elements: ObjectList<E>) {
+        elements.forEach { element ->
+            plusAssign(element)
+        }
+    }
+
+    /**
      * Removes the specified [element] from the set.
      * @param element The element to be removed from the set.
      * @return `true` if the [element] was present in the set, or `false` if it wasn't
@@ -724,6 +776,17 @@
 
     /**
      * Removes the specified [elements] from the set, if present.
+     * @param elements An [ObjectList] whose elements should 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: ObjectList<E>): 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: Array<out E>) {
@@ -763,6 +826,16 @@
     }
 
     /**
+     * Removes the specified [elements] from the set, if present.
+     * @param elements An [ObjectList] whose elements should be removed from the set.
+     */
+    public operator fun minusAssign(elements: ObjectList<E>) {
+        elements.forEach { element ->
+            minusAssign(element)
+        }
+    }
+
+    /**
      * Removes any values for which the specified [predicate] returns true.
      */
     public inline fun removeIf(predicate: (E) -> Boolean) {
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatFloatMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatFloatMapTest.kt
index fa56a25..2e9661e 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatFloatMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatFloatMapTest.kt
@@ -78,14 +78,113 @@
     }
 
     @Test
-    fun floatFloatMapPairsFunction() {
-        val map = mutableFloatFloatMapOf(
-            1f to 1f,
-            2f to 2f
+    fun floatFloatMapInitFunction() {
+        val map1 = floatFloatMapOf(
+            1f, 1f,
         )
-        assertEquals(2, map.size)
-        assertEquals(1f, map[1f])
-        assertEquals(2f, map[2f])
+        assertEquals(1, map1.size)
+        assertEquals(1f, map1[1f])
+
+        val map2 = floatFloatMapOf(
+            1f, 1f,
+            2f, 2f,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1f, map2[1f])
+        assertEquals(2f, map2[2f])
+
+        val map3 = floatFloatMapOf(
+            1f, 1f,
+            2f, 2f,
+            3f, 3f,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1f, map3[1f])
+        assertEquals(2f, map3[2f])
+        assertEquals(3f, map3[3f])
+
+        val map4 = floatFloatMapOf(
+            1f, 1f,
+            2f, 2f,
+            3f, 3f,
+            4f, 4f,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1f, map4[1f])
+        assertEquals(2f, map4[2f])
+        assertEquals(3f, map4[3f])
+        assertEquals(4f, map4[4f])
+
+        val map5 = floatFloatMapOf(
+            1f, 1f,
+            2f, 2f,
+            3f, 3f,
+            4f, 4f,
+            5f, 5f,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1f, map5[1f])
+        assertEquals(2f, map5[2f])
+        assertEquals(3f, map5[3f])
+        assertEquals(4f, map5[4f])
+        assertEquals(5f, map5[5f])
+    }
+
+    @Test
+    fun mutableFloatFloatMapInitFunction() {
+        val map1 = mutableFloatFloatMapOf(
+            1f, 1f,
+        )
+        assertEquals(1, map1.size)
+        assertEquals(1f, map1[1f])
+
+        val map2 = mutableFloatFloatMapOf(
+            1f, 1f,
+            2f, 2f,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1f, map2[1f])
+        assertEquals(2f, map2[2f])
+
+        val map3 = mutableFloatFloatMapOf(
+            1f, 1f,
+            2f, 2f,
+            3f, 3f,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1f, map3[1f])
+        assertEquals(2f, map3[2f])
+        assertEquals(3f, map3[3f])
+
+        val map4 = mutableFloatFloatMapOf(
+            1f, 1f,
+            2f, 2f,
+            3f, 3f,
+            4f, 4f,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1f, map4[1f])
+        assertEquals(2f, map4[2f])
+        assertEquals(3f, map4[3f])
+        assertEquals(4f, map4[4f])
+
+        val map5 = mutableFloatFloatMapOf(
+            1f, 1f,
+            2f, 2f,
+            3f, 3f,
+            4f, 4f,
+            5f, 5f,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1f, map5[1f])
+        assertEquals(2f, map5[2f])
+        assertEquals(3f, map5[3f])
+        assertEquals(4f, map5[4f])
+        assertEquals(5f, map5[5f])
     }
 
     @Test
@@ -146,38 +245,6 @@
     }
 
     @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
@@ -468,6 +535,43 @@
     }
 
     @Test
+    fun joinToString() {
+        val map = MutableFloatFloatMap()
+        repeat(5) {
+            map[it.toFloat()] = it.toFloat()
+        }
+        val order = IntArray(5)
+        var index = 0
+        map.forEach { key, _ ->
+            order[index++] = key.toInt()
+        }
+        assertEquals(
+            "${order[0].toFloat()}=${order[0].toFloat()}, ${order[1].toFloat()}=" +
+            "${order[1].toFloat()}, ${order[2].toFloat()}=${order[2].toFloat()}," +
+            " ${order[3].toFloat()}=${order[3].toFloat()}, ${order[4].toFloat()}=" +
+            "${order[4].toFloat()}",
+            map.joinToString()
+        )
+        assertEquals(
+            "x${order[0].toFloat()}=${order[0].toFloat()}, ${order[1].toFloat()}=" +
+            "${order[1].toFloat()}, ${order[2].toFloat()}=${order[2].toFloat()}...",
+            map.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${order[0].toFloat()}=${order[0].toFloat()}-${order[1].toFloat()}=" +
+            "${order[1].toFloat()}-${order[2].toFloat()}=${order[2].toFloat()}-" +
+            "${order[3].toFloat()}=${order[3].toFloat()}-${order[4].toFloat()}=" +
+            "${order[4].toFloat()}<",
+            map.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        val names = arrayOf("one", "two", "three", "four", "five")
+        assertEquals(
+            "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+            map.joinToString(limit = 3) { key, _ -> names[key.toInt()] }
+        )
+    }
+
+    @Test
     fun equals() {
         val map = MutableFloatFloatMap()
         map[1f] = 1f
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatIntMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatIntMapTest.kt
index 9d24ffc..f895a0c 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatIntMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatIntMapTest.kt
@@ -78,14 +78,113 @@
     }
 
     @Test
-    fun floatIntMapPairsFunction() {
-        val map = mutableFloatIntMapOf(
-            1f to 1,
-            2f to 2
+    fun floatIntMapInitFunction() {
+        val map1 = floatIntMapOf(
+            1f, 1,
         )
-        assertEquals(2, map.size)
-        assertEquals(1, map[1f])
-        assertEquals(2, map[2f])
+        assertEquals(1, map1.size)
+        assertEquals(1, map1[1f])
+
+        val map2 = floatIntMapOf(
+            1f, 1,
+            2f, 2,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1, map2[1f])
+        assertEquals(2, map2[2f])
+
+        val map3 = floatIntMapOf(
+            1f, 1,
+            2f, 2,
+            3f, 3,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1, map3[1f])
+        assertEquals(2, map3[2f])
+        assertEquals(3, map3[3f])
+
+        val map4 = floatIntMapOf(
+            1f, 1,
+            2f, 2,
+            3f, 3,
+            4f, 4,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1, map4[1f])
+        assertEquals(2, map4[2f])
+        assertEquals(3, map4[3f])
+        assertEquals(4, map4[4f])
+
+        val map5 = floatIntMapOf(
+            1f, 1,
+            2f, 2,
+            3f, 3,
+            4f, 4,
+            5f, 5,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1, map5[1f])
+        assertEquals(2, map5[2f])
+        assertEquals(3, map5[3f])
+        assertEquals(4, map5[4f])
+        assertEquals(5, map5[5f])
+    }
+
+    @Test
+    fun mutableFloatIntMapInitFunction() {
+        val map1 = mutableFloatIntMapOf(
+            1f, 1,
+        )
+        assertEquals(1, map1.size)
+        assertEquals(1, map1[1f])
+
+        val map2 = mutableFloatIntMapOf(
+            1f, 1,
+            2f, 2,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1, map2[1f])
+        assertEquals(2, map2[2f])
+
+        val map3 = mutableFloatIntMapOf(
+            1f, 1,
+            2f, 2,
+            3f, 3,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1, map3[1f])
+        assertEquals(2, map3[2f])
+        assertEquals(3, map3[3f])
+
+        val map4 = mutableFloatIntMapOf(
+            1f, 1,
+            2f, 2,
+            3f, 3,
+            4f, 4,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1, map4[1f])
+        assertEquals(2, map4[2f])
+        assertEquals(3, map4[3f])
+        assertEquals(4, map4[4f])
+
+        val map5 = mutableFloatIntMapOf(
+            1f, 1,
+            2f, 2,
+            3f, 3,
+            4f, 4,
+            5f, 5,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1, map5[1f])
+        assertEquals(2, map5[2f])
+        assertEquals(3, map5[3f])
+        assertEquals(4, map5[4f])
+        assertEquals(5, map5[5f])
     }
 
     @Test
@@ -146,38 +245,6 @@
     }
 
     @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
@@ -468,6 +535,43 @@
     }
 
     @Test
+    fun joinToString() {
+        val map = MutableFloatIntMap()
+        repeat(5) {
+            map[it.toFloat()] = it.toInt()
+        }
+        val order = IntArray(5)
+        var index = 0
+        map.forEach { key, _ ->
+            order[index++] = key.toInt()
+        }
+        assertEquals(
+            "${order[0].toFloat()}=${order[0].toInt()}, ${order[1].toFloat()}=" +
+            "${order[1].toInt()}, ${order[2].toFloat()}=${order[2].toInt()}," +
+            " ${order[3].toFloat()}=${order[3].toInt()}, ${order[4].toFloat()}=" +
+            "${order[4].toInt()}",
+            map.joinToString()
+        )
+        assertEquals(
+            "x${order[0].toFloat()}=${order[0].toInt()}, ${order[1].toFloat()}=" +
+            "${order[1].toInt()}, ${order[2].toFloat()}=${order[2].toInt()}...",
+            map.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${order[0].toFloat()}=${order[0].toInt()}-${order[1].toFloat()}=" +
+            "${order[1].toInt()}-${order[2].toFloat()}=${order[2].toInt()}-" +
+            "${order[3].toFloat()}=${order[3].toInt()}-${order[4].toFloat()}=" +
+            "${order[4].toInt()}<",
+            map.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        val names = arrayOf("one", "two", "three", "four", "five")
+        assertEquals(
+            "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+            map.joinToString(limit = 3) { key, _ -> names[key.toInt()] }
+        )
+    }
+
+    @Test
     fun equals() {
         val map = MutableFloatIntMap()
         map[1f] = 1
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatListTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatListTest.kt
index 12e6d66..f0f7cb1 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatListTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatListTest.kt
@@ -93,6 +93,27 @@
     }
 
     @Test
+    fun joinToString() {
+        assertEquals("${1f}, ${2f}, ${3f}, ${4f}, ${5f}", list.joinToString())
+        assertEquals(
+            "x${1f}, ${2f}, ${3f}...",
+            list.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${1f}-${2f}-${3f}-${4f}-${5f}<",
+            list.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        assertEquals("one, two, three...", list.joinToString(limit = 3) {
+            when (it.toInt()) {
+                1 -> "one"
+                2 -> "two"
+                3 -> "three"
+                else -> "whoops"
+            }
+        })
+    }
+
+    @Test
     fun size() {
         assertEquals(5, list.size)
         assertEquals(5, list.count())
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatLongMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatLongMapTest.kt
index cc90959..4436132 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatLongMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatLongMapTest.kt
@@ -78,14 +78,113 @@
     }
 
     @Test
-    fun floatLongMapPairsFunction() {
-        val map = mutableFloatLongMapOf(
-            1f to 1L,
-            2f to 2L
+    fun floatLongMapInitFunction() {
+        val map1 = floatLongMapOf(
+            1f, 1L,
         )
-        assertEquals(2, map.size)
-        assertEquals(1L, map[1f])
-        assertEquals(2L, map[2f])
+        assertEquals(1, map1.size)
+        assertEquals(1L, map1[1f])
+
+        val map2 = floatLongMapOf(
+            1f, 1L,
+            2f, 2L,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1L, map2[1f])
+        assertEquals(2L, map2[2f])
+
+        val map3 = floatLongMapOf(
+            1f, 1L,
+            2f, 2L,
+            3f, 3L,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1L, map3[1f])
+        assertEquals(2L, map3[2f])
+        assertEquals(3L, map3[3f])
+
+        val map4 = floatLongMapOf(
+            1f, 1L,
+            2f, 2L,
+            3f, 3L,
+            4f, 4L,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1L, map4[1f])
+        assertEquals(2L, map4[2f])
+        assertEquals(3L, map4[3f])
+        assertEquals(4L, map4[4f])
+
+        val map5 = floatLongMapOf(
+            1f, 1L,
+            2f, 2L,
+            3f, 3L,
+            4f, 4L,
+            5f, 5L,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1L, map5[1f])
+        assertEquals(2L, map5[2f])
+        assertEquals(3L, map5[3f])
+        assertEquals(4L, map5[4f])
+        assertEquals(5L, map5[5f])
+    }
+
+    @Test
+    fun mutableFloatLongMapInitFunction() {
+        val map1 = mutableFloatLongMapOf(
+            1f, 1L,
+        )
+        assertEquals(1, map1.size)
+        assertEquals(1L, map1[1f])
+
+        val map2 = mutableFloatLongMapOf(
+            1f, 1L,
+            2f, 2L,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1L, map2[1f])
+        assertEquals(2L, map2[2f])
+
+        val map3 = mutableFloatLongMapOf(
+            1f, 1L,
+            2f, 2L,
+            3f, 3L,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1L, map3[1f])
+        assertEquals(2L, map3[2f])
+        assertEquals(3L, map3[3f])
+
+        val map4 = mutableFloatLongMapOf(
+            1f, 1L,
+            2f, 2L,
+            3f, 3L,
+            4f, 4L,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1L, map4[1f])
+        assertEquals(2L, map4[2f])
+        assertEquals(3L, map4[3f])
+        assertEquals(4L, map4[4f])
+
+        val map5 = mutableFloatLongMapOf(
+            1f, 1L,
+            2f, 2L,
+            3f, 3L,
+            4f, 4L,
+            5f, 5L,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1L, map5[1f])
+        assertEquals(2L, map5[2f])
+        assertEquals(3L, map5[3f])
+        assertEquals(4L, map5[4f])
+        assertEquals(5L, map5[5f])
     }
 
     @Test
@@ -146,38 +245,6 @@
     }
 
     @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
@@ -468,6 +535,43 @@
     }
 
     @Test
+    fun joinToString() {
+        val map = MutableFloatLongMap()
+        repeat(5) {
+            map[it.toFloat()] = it.toLong()
+        }
+        val order = IntArray(5)
+        var index = 0
+        map.forEach { key, _ ->
+            order[index++] = key.toInt()
+        }
+        assertEquals(
+            "${order[0].toFloat()}=${order[0].toLong()}, ${order[1].toFloat()}=" +
+            "${order[1].toLong()}, ${order[2].toFloat()}=${order[2].toLong()}," +
+            " ${order[3].toFloat()}=${order[3].toLong()}, ${order[4].toFloat()}=" +
+            "${order[4].toLong()}",
+            map.joinToString()
+        )
+        assertEquals(
+            "x${order[0].toFloat()}=${order[0].toLong()}, ${order[1].toFloat()}=" +
+            "${order[1].toLong()}, ${order[2].toFloat()}=${order[2].toLong()}...",
+            map.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${order[0].toFloat()}=${order[0].toLong()}-${order[1].toFloat()}=" +
+            "${order[1].toLong()}-${order[2].toFloat()}=${order[2].toLong()}-" +
+            "${order[3].toFloat()}=${order[3].toLong()}-${order[4].toFloat()}=" +
+            "${order[4].toLong()}<",
+            map.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        val names = arrayOf("one", "two", "three", "four", "five")
+        assertEquals(
+            "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+            map.joinToString(limit = 3) { key, _ -> names[key.toInt()] }
+        )
+    }
+
+    @Test
     fun equals() {
         val map = MutableFloatLongMap()
         map[1f] = 1L
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatObjectMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatObjectMapTest.kt
index 4a10fc7b..44fe178 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatObjectMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatObjectMapTest.kt
@@ -78,14 +78,113 @@
     }
 
     @Test
-    fun floatObjectMapPairsFunction() {
-        val map = mutableFloatObjectMapOf(
-            1f to "World",
-            2f to "Monde"
+    fun floatObjectMapInitFunction() {
+        val map1 = floatObjectMapOf(
+            1f, "World",
         )
-        assertEquals(2, map.size)
-        assertEquals("World", map[1f])
-        assertEquals("Monde", map[2f])
+        assertEquals(1, map1.size)
+        assertEquals("World", map1[1f])
+
+        val map2 = floatObjectMapOf(
+            1f, "World",
+            2f, "Monde",
+        )
+        assertEquals(2, map2.size)
+        assertEquals("World", map2[1f])
+        assertEquals("Monde", map2[2f])
+
+        val map3 = floatObjectMapOf(
+            1f, "World",
+            2f, "Monde",
+            3f, "Welt",
+        )
+        assertEquals(3, map3.size)
+        assertEquals("World", map3[1f])
+        assertEquals("Monde", map3[2f])
+        assertEquals("Welt", map3[3f])
+
+        val map4 = floatObjectMapOf(
+            1f, "World",
+            2f, "Monde",
+            3f, "Welt",
+            4f, "Sekai",
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals("World", map4[1f])
+        assertEquals("Monde", map4[2f])
+        assertEquals("Welt", map4[3f])
+        assertEquals("Sekai", map4[4f])
+
+        val map5 = floatObjectMapOf(
+            1f, "World",
+            2f, "Monde",
+            3f, "Welt",
+            4f, "Sekai",
+            5f, "Mondo",
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals("World", map5[1f])
+        assertEquals("Monde", map5[2f])
+        assertEquals("Welt", map5[3f])
+        assertEquals("Sekai", map5[4f])
+        assertEquals("Mondo", map5[5f])
+    }
+
+    @Test
+    fun mutableFloatObjectMapInitFunction() {
+        val map1 = mutableFloatObjectMapOf(
+            1f, "World",
+        )
+        assertEquals(1, map1.size)
+        assertEquals("World", map1[1f])
+
+        val map2 = mutableFloatObjectMapOf(
+            1f, "World",
+            2f, "Monde",
+        )
+        assertEquals(2, map2.size)
+        assertEquals("World", map2[1f])
+        assertEquals("Monde", map2[2f])
+
+        val map3 = mutableFloatObjectMapOf(
+            1f, "World",
+            2f, "Monde",
+            3f, "Welt",
+        )
+        assertEquals(3, map3.size)
+        assertEquals("World", map3[1f])
+        assertEquals("Monde", map3[2f])
+        assertEquals("Welt", map3[3f])
+
+        val map4 = mutableFloatObjectMapOf(
+            1f, "World",
+            2f, "Monde",
+            3f, "Welt",
+            4f, "Sekai",
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals("World", map4[1f])
+        assertEquals("Monde", map4[2f])
+        assertEquals("Welt", map4[3f])
+        assertEquals("Sekai", map4[4f])
+
+        val map5 = mutableFloatObjectMapOf(
+            1f, "World",
+            2f, "Monde",
+            3f, "Welt",
+            4f, "Sekai",
+            5f, "Mondo",
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals("World", map5[1f])
+        assertEquals("Monde", map5[2f])
+        assertEquals("Welt", map5[3f])
+        assertEquals("Sekai", map5[4f])
+        assertEquals("Mondo", map5[5f])
     }
 
     @Test
@@ -153,25 +252,12 @@
     }
 
     @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"))
+        map.putAll(mutableFloatObjectMapOf(3f, "Welt", 7f, "Mundo"))
 
         assertEquals(4, map.size)
         assertEquals("Welt", map[3f])
@@ -179,28 +265,9 @@
     }
 
     @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")
+        map += floatObjectMapOf(3f, "Welt", 7f, "Mundo")
 
         assertEquals(2, map.size)
         assertEquals("Welt", map[3f])
@@ -520,6 +587,41 @@
     }
 
     @Test
+    fun joinToString() {
+        val map = MutableFloatObjectMap<String>()
+        repeat(5) {
+            map[it.toFloat()] = it.toString()
+        }
+        val order = IntArray(5)
+        var index = 0
+        map.forEach { key, _ ->
+            order[index++] = key.toInt()
+        }
+        assertEquals(
+            "${order[0].toFloat()}=${order[0]}, ${order[1].toFloat()}=${order[1]}, " +
+            "${order[2].toFloat()}=${order[2]}, ${order[3].toFloat()}=${order[3]}, " +
+            "${order[4].toFloat()}=${order[4]}",
+            map.joinToString()
+        )
+        assertEquals(
+            "x${order[0].toFloat()}=${order[0]}, ${order[1].toFloat()}=${order[1]}, " +
+            "${order[2].toFloat()}=${order[2]}...",
+            map.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${order[0].toFloat()}=${order[0]}-${order[1].toFloat()}=${order[1]}-" +
+            "${order[2].toFloat()}=${order[2]}-${order[3].toFloat()}=${order[3]}-" +
+            "${order[4].toFloat()}=${order[4]}<",
+            map.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        val names = arrayOf("one", "two", "three", "four", "five")
+        assertEquals(
+            "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+            map.joinToString(limit = 3) { key, _ -> names[key.toInt()] }
+        )
+    }
+
+    @Test
     fun equals() {
         val map = MutableFloatObjectMap<String?>()
         map[1f] = "World"
@@ -626,7 +728,7 @@
         map[5f] = "Mondo"
         map[6f] = "Sesang"
 
-        assertTrue(map.all { key, value -> key < 7f && value.length > 0 })
+        assertTrue(map.all { key, value -> key < 7f && value.isNotEmpty() })
         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 af07640..767b2f0 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatSetTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatSetTest.kt
@@ -347,6 +347,35 @@
     }
 
     @Test
+    fun joinToString() {
+        val set = floatSetOf(1f, 2f, 3f, 4f, 5f)
+        val order = IntArray(5)
+        var index = 0
+        set.forEach { element ->
+            order[index++] = element.toInt()
+        }
+        assertEquals(
+            "${order[0].toFloat()}, ${order[1].toFloat()}, ${order[2].toFloat()}, " +
+            "${order[3].toFloat()}, ${order[4].toFloat()}",
+            set.joinToString()
+        )
+        assertEquals(
+            "x${order[0].toFloat()}, ${order[1].toFloat()}, ${order[2].toFloat()}...",
+            set.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${order[0].toFloat()}-${order[1].toFloat()}-${order[2].toFloat()}-" +
+            "${order[3].toFloat()}-${order[4].toFloat()}<",
+            set.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        val names = arrayOf("one", "two", "three", "four", "five")
+        assertEquals(
+            "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+            set.joinToString(limit = 3) { names[it.toInt()] }
+        )
+    }
+
+    @Test
     fun equals() {
         val set = MutableFloatSet()
         set += 1f
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/IntFloatMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/IntFloatMapTest.kt
index 94c8ec2..0b675f6 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/IntFloatMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntFloatMapTest.kt
@@ -78,14 +78,113 @@
     }
 
     @Test
-    fun intFloatMapPairsFunction() {
-        val map = mutableIntFloatMapOf(
-            1 to 1f,
-            2 to 2f
+    fun intFloatMapInitFunction() {
+        val map1 = intFloatMapOf(
+            1, 1f,
         )
-        assertEquals(2, map.size)
-        assertEquals(1f, map[1])
-        assertEquals(2f, map[2])
+        assertEquals(1, map1.size)
+        assertEquals(1f, map1[1])
+
+        val map2 = intFloatMapOf(
+            1, 1f,
+            2, 2f,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1f, map2[1])
+        assertEquals(2f, map2[2])
+
+        val map3 = intFloatMapOf(
+            1, 1f,
+            2, 2f,
+            3, 3f,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1f, map3[1])
+        assertEquals(2f, map3[2])
+        assertEquals(3f, map3[3])
+
+        val map4 = intFloatMapOf(
+            1, 1f,
+            2, 2f,
+            3, 3f,
+            4, 4f,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1f, map4[1])
+        assertEquals(2f, map4[2])
+        assertEquals(3f, map4[3])
+        assertEquals(4f, map4[4])
+
+        val map5 = intFloatMapOf(
+            1, 1f,
+            2, 2f,
+            3, 3f,
+            4, 4f,
+            5, 5f,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1f, map5[1])
+        assertEquals(2f, map5[2])
+        assertEquals(3f, map5[3])
+        assertEquals(4f, map5[4])
+        assertEquals(5f, map5[5])
+    }
+
+    @Test
+    fun mutableIntFloatMapInitFunction() {
+        val map1 = mutableIntFloatMapOf(
+            1, 1f,
+        )
+        assertEquals(1, map1.size)
+        assertEquals(1f, map1[1])
+
+        val map2 = mutableIntFloatMapOf(
+            1, 1f,
+            2, 2f,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1f, map2[1])
+        assertEquals(2f, map2[2])
+
+        val map3 = mutableIntFloatMapOf(
+            1, 1f,
+            2, 2f,
+            3, 3f,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1f, map3[1])
+        assertEquals(2f, map3[2])
+        assertEquals(3f, map3[3])
+
+        val map4 = mutableIntFloatMapOf(
+            1, 1f,
+            2, 2f,
+            3, 3f,
+            4, 4f,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1f, map4[1])
+        assertEquals(2f, map4[2])
+        assertEquals(3f, map4[3])
+        assertEquals(4f, map4[4])
+
+        val map5 = mutableIntFloatMapOf(
+            1, 1f,
+            2, 2f,
+            3, 3f,
+            4, 4f,
+            5, 5f,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1f, map5[1])
+        assertEquals(2f, map5[2])
+        assertEquals(3f, map5[3])
+        assertEquals(4f, map5[4])
+        assertEquals(5f, map5[5])
     }
 
     @Test
@@ -146,38 +245,6 @@
     }
 
     @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
@@ -468,6 +535,43 @@
     }
 
     @Test
+    fun joinToString() {
+        val map = MutableIntFloatMap()
+        repeat(5) {
+            map[it.toInt()] = it.toFloat()
+        }
+        val order = IntArray(5)
+        var index = 0
+        map.forEach { key, _ ->
+            order[index++] = key.toInt()
+        }
+        assertEquals(
+            "${order[0].toInt()}=${order[0].toFloat()}, ${order[1].toInt()}=" +
+            "${order[1].toFloat()}, ${order[2].toInt()}=${order[2].toFloat()}," +
+            " ${order[3].toInt()}=${order[3].toFloat()}, ${order[4].toInt()}=" +
+            "${order[4].toFloat()}",
+            map.joinToString()
+        )
+        assertEquals(
+            "x${order[0].toInt()}=${order[0].toFloat()}, ${order[1].toInt()}=" +
+            "${order[1].toFloat()}, ${order[2].toInt()}=${order[2].toFloat()}...",
+            map.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${order[0].toInt()}=${order[0].toFloat()}-${order[1].toInt()}=" +
+            "${order[1].toFloat()}-${order[2].toInt()}=${order[2].toFloat()}-" +
+            "${order[3].toInt()}=${order[3].toFloat()}-${order[4].toInt()}=" +
+            "${order[4].toFloat()}<",
+            map.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        val names = arrayOf("one", "two", "three", "four", "five")
+        assertEquals(
+            "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+            map.joinToString(limit = 3) { key, _ -> names[key.toInt()] }
+        )
+    }
+
+    @Test
     fun equals() {
         val map = MutableIntFloatMap()
         map[1] = 1f
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/IntIntMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/IntIntMapTest.kt
index 933c5ba..37ffbfe 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/IntIntMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntIntMapTest.kt
@@ -78,14 +78,113 @@
     }
 
     @Test
-    fun intIntMapPairsFunction() {
-        val map = mutableIntIntMapOf(
-            1 to 1,
-            2 to 2
+    fun intIntMapInitFunction() {
+        val map1 = intIntMapOf(
+            1, 1,
         )
-        assertEquals(2, map.size)
-        assertEquals(1, map[1])
-        assertEquals(2, map[2])
+        assertEquals(1, map1.size)
+        assertEquals(1, map1[1])
+
+        val map2 = intIntMapOf(
+            1, 1,
+            2, 2,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1, map2[1])
+        assertEquals(2, map2[2])
+
+        val map3 = intIntMapOf(
+            1, 1,
+            2, 2,
+            3, 3,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1, map3[1])
+        assertEquals(2, map3[2])
+        assertEquals(3, map3[3])
+
+        val map4 = intIntMapOf(
+            1, 1,
+            2, 2,
+            3, 3,
+            4, 4,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1, map4[1])
+        assertEquals(2, map4[2])
+        assertEquals(3, map4[3])
+        assertEquals(4, map4[4])
+
+        val map5 = intIntMapOf(
+            1, 1,
+            2, 2,
+            3, 3,
+            4, 4,
+            5, 5,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1, map5[1])
+        assertEquals(2, map5[2])
+        assertEquals(3, map5[3])
+        assertEquals(4, map5[4])
+        assertEquals(5, map5[5])
+    }
+
+    @Test
+    fun mutableIntIntMapInitFunction() {
+        val map1 = mutableIntIntMapOf(
+            1, 1,
+        )
+        assertEquals(1, map1.size)
+        assertEquals(1, map1[1])
+
+        val map2 = mutableIntIntMapOf(
+            1, 1,
+            2, 2,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1, map2[1])
+        assertEquals(2, map2[2])
+
+        val map3 = mutableIntIntMapOf(
+            1, 1,
+            2, 2,
+            3, 3,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1, map3[1])
+        assertEquals(2, map3[2])
+        assertEquals(3, map3[3])
+
+        val map4 = mutableIntIntMapOf(
+            1, 1,
+            2, 2,
+            3, 3,
+            4, 4,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1, map4[1])
+        assertEquals(2, map4[2])
+        assertEquals(3, map4[3])
+        assertEquals(4, map4[4])
+
+        val map5 = mutableIntIntMapOf(
+            1, 1,
+            2, 2,
+            3, 3,
+            4, 4,
+            5, 5,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1, map5[1])
+        assertEquals(2, map5[2])
+        assertEquals(3, map5[3])
+        assertEquals(4, map5[4])
+        assertEquals(5, map5[5])
     }
 
     @Test
@@ -146,38 +245,6 @@
     }
 
     @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
@@ -468,6 +535,43 @@
     }
 
     @Test
+    fun joinToString() {
+        val map = MutableIntIntMap()
+        repeat(5) {
+            map[it.toInt()] = it.toInt()
+        }
+        val order = IntArray(5)
+        var index = 0
+        map.forEach { key, _ ->
+            order[index++] = key.toInt()
+        }
+        assertEquals(
+            "${order[0].toInt()}=${order[0].toInt()}, ${order[1].toInt()}=" +
+            "${order[1].toInt()}, ${order[2].toInt()}=${order[2].toInt()}," +
+            " ${order[3].toInt()}=${order[3].toInt()}, ${order[4].toInt()}=" +
+            "${order[4].toInt()}",
+            map.joinToString()
+        )
+        assertEquals(
+            "x${order[0].toInt()}=${order[0].toInt()}, ${order[1].toInt()}=" +
+            "${order[1].toInt()}, ${order[2].toInt()}=${order[2].toInt()}...",
+            map.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${order[0].toInt()}=${order[0].toInt()}-${order[1].toInt()}=" +
+            "${order[1].toInt()}-${order[2].toInt()}=${order[2].toInt()}-" +
+            "${order[3].toInt()}=${order[3].toInt()}-${order[4].toInt()}=" +
+            "${order[4].toInt()}<",
+            map.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        val names = arrayOf("one", "two", "three", "four", "five")
+        assertEquals(
+            "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+            map.joinToString(limit = 3) { key, _ -> names[key.toInt()] }
+        )
+    }
+
+    @Test
     fun equals() {
         val map = MutableIntIntMap()
         map[1] = 1
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/IntListTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/IntListTest.kt
index 7a613eb..f1688d0 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/IntListTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntListTest.kt
@@ -93,6 +93,27 @@
     }
 
     @Test
+    fun joinToString() {
+        assertEquals("${1}, ${2}, ${3}, ${4}, ${5}", list.joinToString())
+        assertEquals(
+            "x${1}, ${2}, ${3}...",
+            list.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${1}-${2}-${3}-${4}-${5}<",
+            list.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        assertEquals("one, two, three...", list.joinToString(limit = 3) {
+            when (it.toInt()) {
+                1 -> "one"
+                2 -> "two"
+                3 -> "three"
+                else -> "whoops"
+            }
+        })
+    }
+
+    @Test
     fun size() {
         assertEquals(5, list.size)
         assertEquals(5, list.count())
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/IntLongMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/IntLongMapTest.kt
index 734fb05..32add2e 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/IntLongMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntLongMapTest.kt
@@ -78,14 +78,113 @@
     }
 
     @Test
-    fun intLongMapPairsFunction() {
-        val map = mutableIntLongMapOf(
-            1 to 1L,
-            2 to 2L
+    fun intLongMapInitFunction() {
+        val map1 = intLongMapOf(
+            1, 1L,
         )
-        assertEquals(2, map.size)
-        assertEquals(1L, map[1])
-        assertEquals(2L, map[2])
+        assertEquals(1, map1.size)
+        assertEquals(1L, map1[1])
+
+        val map2 = intLongMapOf(
+            1, 1L,
+            2, 2L,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1L, map2[1])
+        assertEquals(2L, map2[2])
+
+        val map3 = intLongMapOf(
+            1, 1L,
+            2, 2L,
+            3, 3L,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1L, map3[1])
+        assertEquals(2L, map3[2])
+        assertEquals(3L, map3[3])
+
+        val map4 = intLongMapOf(
+            1, 1L,
+            2, 2L,
+            3, 3L,
+            4, 4L,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1L, map4[1])
+        assertEquals(2L, map4[2])
+        assertEquals(3L, map4[3])
+        assertEquals(4L, map4[4])
+
+        val map5 = intLongMapOf(
+            1, 1L,
+            2, 2L,
+            3, 3L,
+            4, 4L,
+            5, 5L,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1L, map5[1])
+        assertEquals(2L, map5[2])
+        assertEquals(3L, map5[3])
+        assertEquals(4L, map5[4])
+        assertEquals(5L, map5[5])
+    }
+
+    @Test
+    fun mutableIntLongMapInitFunction() {
+        val map1 = mutableIntLongMapOf(
+            1, 1L,
+        )
+        assertEquals(1, map1.size)
+        assertEquals(1L, map1[1])
+
+        val map2 = mutableIntLongMapOf(
+            1, 1L,
+            2, 2L,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1L, map2[1])
+        assertEquals(2L, map2[2])
+
+        val map3 = mutableIntLongMapOf(
+            1, 1L,
+            2, 2L,
+            3, 3L,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1L, map3[1])
+        assertEquals(2L, map3[2])
+        assertEquals(3L, map3[3])
+
+        val map4 = mutableIntLongMapOf(
+            1, 1L,
+            2, 2L,
+            3, 3L,
+            4, 4L,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1L, map4[1])
+        assertEquals(2L, map4[2])
+        assertEquals(3L, map4[3])
+        assertEquals(4L, map4[4])
+
+        val map5 = mutableIntLongMapOf(
+            1, 1L,
+            2, 2L,
+            3, 3L,
+            4, 4L,
+            5, 5L,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1L, map5[1])
+        assertEquals(2L, map5[2])
+        assertEquals(3L, map5[3])
+        assertEquals(4L, map5[4])
+        assertEquals(5L, map5[5])
     }
 
     @Test
@@ -146,38 +245,6 @@
     }
 
     @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
@@ -468,6 +535,43 @@
     }
 
     @Test
+    fun joinToString() {
+        val map = MutableIntLongMap()
+        repeat(5) {
+            map[it.toInt()] = it.toLong()
+        }
+        val order = IntArray(5)
+        var index = 0
+        map.forEach { key, _ ->
+            order[index++] = key.toInt()
+        }
+        assertEquals(
+            "${order[0].toInt()}=${order[0].toLong()}, ${order[1].toInt()}=" +
+            "${order[1].toLong()}, ${order[2].toInt()}=${order[2].toLong()}," +
+            " ${order[3].toInt()}=${order[3].toLong()}, ${order[4].toInt()}=" +
+            "${order[4].toLong()}",
+            map.joinToString()
+        )
+        assertEquals(
+            "x${order[0].toInt()}=${order[0].toLong()}, ${order[1].toInt()}=" +
+            "${order[1].toLong()}, ${order[2].toInt()}=${order[2].toLong()}...",
+            map.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${order[0].toInt()}=${order[0].toLong()}-${order[1].toInt()}=" +
+            "${order[1].toLong()}-${order[2].toInt()}=${order[2].toLong()}-" +
+            "${order[3].toInt()}=${order[3].toLong()}-${order[4].toInt()}=" +
+            "${order[4].toLong()}<",
+            map.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        val names = arrayOf("one", "two", "three", "four", "five")
+        assertEquals(
+            "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+            map.joinToString(limit = 3) { key, _ -> names[key.toInt()] }
+        )
+    }
+
+    @Test
     fun equals() {
         val map = MutableIntLongMap()
         map[1] = 1L
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/IntObjectMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/IntObjectMapTest.kt
index 40038d2..d5eeee1 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/IntObjectMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntObjectMapTest.kt
@@ -78,14 +78,113 @@
     }
 
     @Test
-    fun intObjectMapPairsFunction() {
-        val map = mutableIntObjectMapOf(
-            1 to "World",
-            2 to "Monde"
+    fun intObjectMapInitFunction() {
+        val map1 = intObjectMapOf(
+            1, "World",
         )
-        assertEquals(2, map.size)
-        assertEquals("World", map[1])
-        assertEquals("Monde", map[2])
+        assertEquals(1, map1.size)
+        assertEquals("World", map1[1])
+
+        val map2 = intObjectMapOf(
+            1, "World",
+            2, "Monde",
+        )
+        assertEquals(2, map2.size)
+        assertEquals("World", map2[1])
+        assertEquals("Monde", map2[2])
+
+        val map3 = intObjectMapOf(
+            1, "World",
+            2, "Monde",
+            3, "Welt",
+        )
+        assertEquals(3, map3.size)
+        assertEquals("World", map3[1])
+        assertEquals("Monde", map3[2])
+        assertEquals("Welt", map3[3])
+
+        val map4 = intObjectMapOf(
+            1, "World",
+            2, "Monde",
+            3, "Welt",
+            4, "Sekai",
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals("World", map4[1])
+        assertEquals("Monde", map4[2])
+        assertEquals("Welt", map4[3])
+        assertEquals("Sekai", map4[4])
+
+        val map5 = intObjectMapOf(
+            1, "World",
+            2, "Monde",
+            3, "Welt",
+            4, "Sekai",
+            5, "Mondo",
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals("World", map5[1])
+        assertEquals("Monde", map5[2])
+        assertEquals("Welt", map5[3])
+        assertEquals("Sekai", map5[4])
+        assertEquals("Mondo", map5[5])
+    }
+
+    @Test
+    fun mutableIntObjectMapInitFunction() {
+        val map1 = mutableIntObjectMapOf(
+            1, "World",
+        )
+        assertEquals(1, map1.size)
+        assertEquals("World", map1[1])
+
+        val map2 = mutableIntObjectMapOf(
+            1, "World",
+            2, "Monde",
+        )
+        assertEquals(2, map2.size)
+        assertEquals("World", map2[1])
+        assertEquals("Monde", map2[2])
+
+        val map3 = mutableIntObjectMapOf(
+            1, "World",
+            2, "Monde",
+            3, "Welt",
+        )
+        assertEquals(3, map3.size)
+        assertEquals("World", map3[1])
+        assertEquals("Monde", map3[2])
+        assertEquals("Welt", map3[3])
+
+        val map4 = mutableIntObjectMapOf(
+            1, "World",
+            2, "Monde",
+            3, "Welt",
+            4, "Sekai",
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals("World", map4[1])
+        assertEquals("Monde", map4[2])
+        assertEquals("Welt", map4[3])
+        assertEquals("Sekai", map4[4])
+
+        val map5 = mutableIntObjectMapOf(
+            1, "World",
+            2, "Monde",
+            3, "Welt",
+            4, "Sekai",
+            5, "Mondo",
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals("World", map5[1])
+        assertEquals("Monde", map5[2])
+        assertEquals("Welt", map5[3])
+        assertEquals("Sekai", map5[4])
+        assertEquals("Mondo", map5[5])
     }
 
     @Test
@@ -153,25 +252,12 @@
     }
 
     @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"))
+        map.putAll(mutableIntObjectMapOf(3, "Welt", 7, "Mundo"))
 
         assertEquals(4, map.size)
         assertEquals("Welt", map[3])
@@ -179,28 +265,9 @@
     }
 
     @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")
+        map += intObjectMapOf(3, "Welt", 7, "Mundo")
 
         assertEquals(2, map.size)
         assertEquals("Welt", map[3])
@@ -520,6 +587,41 @@
     }
 
     @Test
+    fun joinToString() {
+        val map = MutableIntObjectMap<String>()
+        repeat(5) {
+            map[it.toInt()] = it.toString()
+        }
+        val order = IntArray(5)
+        var index = 0
+        map.forEach { key, _ ->
+            order[index++] = key.toInt()
+        }
+        assertEquals(
+            "${order[0].toInt()}=${order[0]}, ${order[1].toInt()}=${order[1]}, " +
+            "${order[2].toInt()}=${order[2]}, ${order[3].toInt()}=${order[3]}, " +
+            "${order[4].toInt()}=${order[4]}",
+            map.joinToString()
+        )
+        assertEquals(
+            "x${order[0].toInt()}=${order[0]}, ${order[1].toInt()}=${order[1]}, " +
+            "${order[2].toInt()}=${order[2]}...",
+            map.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${order[0].toInt()}=${order[0]}-${order[1].toInt()}=${order[1]}-" +
+            "${order[2].toInt()}=${order[2]}-${order[3].toInt()}=${order[3]}-" +
+            "${order[4].toInt()}=${order[4]}<",
+            map.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        val names = arrayOf("one", "two", "three", "four", "five")
+        assertEquals(
+            "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+            map.joinToString(limit = 3) { key, _ -> names[key.toInt()] }
+        )
+    }
+
+    @Test
     fun equals() {
         val map = MutableIntObjectMap<String?>()
         map[1] = "World"
@@ -626,7 +728,7 @@
         map[5] = "Mondo"
         map[6] = "Sesang"
 
-        assertTrue(map.all { key, value -> key < 7 && value.length > 0 })
+        assertTrue(map.all { key, value -> key < 7 && value.isNotEmpty() })
         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 9d55a17..36a2692 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/IntSetTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntSetTest.kt
@@ -347,6 +347,35 @@
     }
 
     @Test
+    fun joinToString() {
+        val set = intSetOf(1, 2, 3, 4, 5)
+        val order = IntArray(5)
+        var index = 0
+        set.forEach { element ->
+            order[index++] = element.toInt()
+        }
+        assertEquals(
+            "${order[0].toInt()}, ${order[1].toInt()}, ${order[2].toInt()}, " +
+            "${order[3].toInt()}, ${order[4].toInt()}",
+            set.joinToString()
+        )
+        assertEquals(
+            "x${order[0].toInt()}, ${order[1].toInt()}, ${order[2].toInt()}...",
+            set.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${order[0].toInt()}-${order[1].toInt()}-${order[2].toInt()}-" +
+            "${order[3].toInt()}-${order[4].toInt()}<",
+            set.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        val names = arrayOf("one", "two", "three", "four", "five")
+        assertEquals(
+            "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+            set.joinToString(limit = 3) { names[it.toInt()] }
+        )
+    }
+
+    @Test
     fun equals() {
         val set = MutableIntSet()
         set += 1
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/LongFloatMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/LongFloatMapTest.kt
index b395753..da332c3 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/LongFloatMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongFloatMapTest.kt
@@ -78,14 +78,113 @@
     }
 
     @Test
-    fun longFloatMapPairsFunction() {
-        val map = mutableLongFloatMapOf(
-            1L to 1f,
-            2L to 2f
+    fun longFloatMapInitFunction() {
+        val map1 = longFloatMapOf(
+            1L, 1f,
         )
-        assertEquals(2, map.size)
-        assertEquals(1f, map[1L])
-        assertEquals(2f, map[2L])
+        assertEquals(1, map1.size)
+        assertEquals(1f, map1[1L])
+
+        val map2 = longFloatMapOf(
+            1L, 1f,
+            2L, 2f,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1f, map2[1L])
+        assertEquals(2f, map2[2L])
+
+        val map3 = longFloatMapOf(
+            1L, 1f,
+            2L, 2f,
+            3L, 3f,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1f, map3[1L])
+        assertEquals(2f, map3[2L])
+        assertEquals(3f, map3[3L])
+
+        val map4 = longFloatMapOf(
+            1L, 1f,
+            2L, 2f,
+            3L, 3f,
+            4L, 4f,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1f, map4[1L])
+        assertEquals(2f, map4[2L])
+        assertEquals(3f, map4[3L])
+        assertEquals(4f, map4[4L])
+
+        val map5 = longFloatMapOf(
+            1L, 1f,
+            2L, 2f,
+            3L, 3f,
+            4L, 4f,
+            5L, 5f,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1f, map5[1L])
+        assertEquals(2f, map5[2L])
+        assertEquals(3f, map5[3L])
+        assertEquals(4f, map5[4L])
+        assertEquals(5f, map5[5L])
+    }
+
+    @Test
+    fun mutableLongFloatMapInitFunction() {
+        val map1 = mutableLongFloatMapOf(
+            1L, 1f,
+        )
+        assertEquals(1, map1.size)
+        assertEquals(1f, map1[1L])
+
+        val map2 = mutableLongFloatMapOf(
+            1L, 1f,
+            2L, 2f,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1f, map2[1L])
+        assertEquals(2f, map2[2L])
+
+        val map3 = mutableLongFloatMapOf(
+            1L, 1f,
+            2L, 2f,
+            3L, 3f,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1f, map3[1L])
+        assertEquals(2f, map3[2L])
+        assertEquals(3f, map3[3L])
+
+        val map4 = mutableLongFloatMapOf(
+            1L, 1f,
+            2L, 2f,
+            3L, 3f,
+            4L, 4f,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1f, map4[1L])
+        assertEquals(2f, map4[2L])
+        assertEquals(3f, map4[3L])
+        assertEquals(4f, map4[4L])
+
+        val map5 = mutableLongFloatMapOf(
+            1L, 1f,
+            2L, 2f,
+            3L, 3f,
+            4L, 4f,
+            5L, 5f,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1f, map5[1L])
+        assertEquals(2f, map5[2L])
+        assertEquals(3f, map5[3L])
+        assertEquals(4f, map5[4L])
+        assertEquals(5f, map5[5L])
     }
 
     @Test
@@ -146,38 +245,6 @@
     }
 
     @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
@@ -468,6 +535,43 @@
     }
 
     @Test
+    fun joinToString() {
+        val map = MutableLongFloatMap()
+        repeat(5) {
+            map[it.toLong()] = it.toFloat()
+        }
+        val order = IntArray(5)
+        var index = 0
+        map.forEach { key, _ ->
+            order[index++] = key.toInt()
+        }
+        assertEquals(
+            "${order[0].toLong()}=${order[0].toFloat()}, ${order[1].toLong()}=" +
+            "${order[1].toFloat()}, ${order[2].toLong()}=${order[2].toFloat()}," +
+            " ${order[3].toLong()}=${order[3].toFloat()}, ${order[4].toLong()}=" +
+            "${order[4].toFloat()}",
+            map.joinToString()
+        )
+        assertEquals(
+            "x${order[0].toLong()}=${order[0].toFloat()}, ${order[1].toLong()}=" +
+            "${order[1].toFloat()}, ${order[2].toLong()}=${order[2].toFloat()}...",
+            map.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${order[0].toLong()}=${order[0].toFloat()}-${order[1].toLong()}=" +
+            "${order[1].toFloat()}-${order[2].toLong()}=${order[2].toFloat()}-" +
+            "${order[3].toLong()}=${order[3].toFloat()}-${order[4].toLong()}=" +
+            "${order[4].toFloat()}<",
+            map.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        val names = arrayOf("one", "two", "three", "four", "five")
+        assertEquals(
+            "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+            map.joinToString(limit = 3) { key, _ -> names[key.toInt()] }
+        )
+    }
+
+    @Test
     fun equals() {
         val map = MutableLongFloatMap()
         map[1L] = 1f
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/LongIntMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/LongIntMapTest.kt
index cf64c64..ec13454 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/LongIntMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongIntMapTest.kt
@@ -78,14 +78,113 @@
     }
 
     @Test
-    fun longIntMapPairsFunction() {
-        val map = mutableLongIntMapOf(
-            1L to 1,
-            2L to 2
+    fun longIntMapInitFunction() {
+        val map1 = longIntMapOf(
+            1L, 1,
         )
-        assertEquals(2, map.size)
-        assertEquals(1, map[1L])
-        assertEquals(2, map[2L])
+        assertEquals(1, map1.size)
+        assertEquals(1, map1[1L])
+
+        val map2 = longIntMapOf(
+            1L, 1,
+            2L, 2,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1, map2[1L])
+        assertEquals(2, map2[2L])
+
+        val map3 = longIntMapOf(
+            1L, 1,
+            2L, 2,
+            3L, 3,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1, map3[1L])
+        assertEquals(2, map3[2L])
+        assertEquals(3, map3[3L])
+
+        val map4 = longIntMapOf(
+            1L, 1,
+            2L, 2,
+            3L, 3,
+            4L, 4,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1, map4[1L])
+        assertEquals(2, map4[2L])
+        assertEquals(3, map4[3L])
+        assertEquals(4, map4[4L])
+
+        val map5 = longIntMapOf(
+            1L, 1,
+            2L, 2,
+            3L, 3,
+            4L, 4,
+            5L, 5,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1, map5[1L])
+        assertEquals(2, map5[2L])
+        assertEquals(3, map5[3L])
+        assertEquals(4, map5[4L])
+        assertEquals(5, map5[5L])
+    }
+
+    @Test
+    fun mutableLongIntMapInitFunction() {
+        val map1 = mutableLongIntMapOf(
+            1L, 1,
+        )
+        assertEquals(1, map1.size)
+        assertEquals(1, map1[1L])
+
+        val map2 = mutableLongIntMapOf(
+            1L, 1,
+            2L, 2,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1, map2[1L])
+        assertEquals(2, map2[2L])
+
+        val map3 = mutableLongIntMapOf(
+            1L, 1,
+            2L, 2,
+            3L, 3,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1, map3[1L])
+        assertEquals(2, map3[2L])
+        assertEquals(3, map3[3L])
+
+        val map4 = mutableLongIntMapOf(
+            1L, 1,
+            2L, 2,
+            3L, 3,
+            4L, 4,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1, map4[1L])
+        assertEquals(2, map4[2L])
+        assertEquals(3, map4[3L])
+        assertEquals(4, map4[4L])
+
+        val map5 = mutableLongIntMapOf(
+            1L, 1,
+            2L, 2,
+            3L, 3,
+            4L, 4,
+            5L, 5,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1, map5[1L])
+        assertEquals(2, map5[2L])
+        assertEquals(3, map5[3L])
+        assertEquals(4, map5[4L])
+        assertEquals(5, map5[5L])
     }
 
     @Test
@@ -146,38 +245,6 @@
     }
 
     @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
@@ -468,6 +535,43 @@
     }
 
     @Test
+    fun joinToString() {
+        val map = MutableLongIntMap()
+        repeat(5) {
+            map[it.toLong()] = it.toInt()
+        }
+        val order = IntArray(5)
+        var index = 0
+        map.forEach { key, _ ->
+            order[index++] = key.toInt()
+        }
+        assertEquals(
+            "${order[0].toLong()}=${order[0].toInt()}, ${order[1].toLong()}=" +
+            "${order[1].toInt()}, ${order[2].toLong()}=${order[2].toInt()}," +
+            " ${order[3].toLong()}=${order[3].toInt()}, ${order[4].toLong()}=" +
+            "${order[4].toInt()}",
+            map.joinToString()
+        )
+        assertEquals(
+            "x${order[0].toLong()}=${order[0].toInt()}, ${order[1].toLong()}=" +
+            "${order[1].toInt()}, ${order[2].toLong()}=${order[2].toInt()}...",
+            map.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${order[0].toLong()}=${order[0].toInt()}-${order[1].toLong()}=" +
+            "${order[1].toInt()}-${order[2].toLong()}=${order[2].toInt()}-" +
+            "${order[3].toLong()}=${order[3].toInt()}-${order[4].toLong()}=" +
+            "${order[4].toInt()}<",
+            map.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        val names = arrayOf("one", "two", "three", "four", "five")
+        assertEquals(
+            "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+            map.joinToString(limit = 3) { key, _ -> names[key.toInt()] }
+        )
+    }
+
+    @Test
     fun equals() {
         val map = MutableLongIntMap()
         map[1L] = 1
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/LongListTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/LongListTest.kt
index 39bde8f..00178592 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/LongListTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongListTest.kt
@@ -93,6 +93,27 @@
     }
 
     @Test
+    fun joinToString() {
+        assertEquals("${1L}, ${2L}, ${3L}, ${4L}, ${5L}", list.joinToString())
+        assertEquals(
+            "x${1L}, ${2L}, ${3L}...",
+            list.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${1L}-${2L}-${3L}-${4L}-${5L}<",
+            list.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        assertEquals("one, two, three...", list.joinToString(limit = 3) {
+            when (it.toInt()) {
+                1 -> "one"
+                2 -> "two"
+                3 -> "three"
+                else -> "whoops"
+            }
+        })
+    }
+
+    @Test
     fun size() {
         assertEquals(5, list.size)
         assertEquals(5, list.count())
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/LongLongMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/LongLongMapTest.kt
index b45ae5a..571c266 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/LongLongMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongLongMapTest.kt
@@ -78,14 +78,113 @@
     }
 
     @Test
-    fun longLongMapPairsFunction() {
-        val map = mutableLongLongMapOf(
-            1L to 1L,
-            2L to 2L
+    fun longLongMapInitFunction() {
+        val map1 = longLongMapOf(
+            1L, 1L,
         )
-        assertEquals(2, map.size)
-        assertEquals(1L, map[1L])
-        assertEquals(2L, map[2L])
+        assertEquals(1, map1.size)
+        assertEquals(1L, map1[1L])
+
+        val map2 = longLongMapOf(
+            1L, 1L,
+            2L, 2L,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1L, map2[1L])
+        assertEquals(2L, map2[2L])
+
+        val map3 = longLongMapOf(
+            1L, 1L,
+            2L, 2L,
+            3L, 3L,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1L, map3[1L])
+        assertEquals(2L, map3[2L])
+        assertEquals(3L, map3[3L])
+
+        val map4 = longLongMapOf(
+            1L, 1L,
+            2L, 2L,
+            3L, 3L,
+            4L, 4L,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1L, map4[1L])
+        assertEquals(2L, map4[2L])
+        assertEquals(3L, map4[3L])
+        assertEquals(4L, map4[4L])
+
+        val map5 = longLongMapOf(
+            1L, 1L,
+            2L, 2L,
+            3L, 3L,
+            4L, 4L,
+            5L, 5L,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1L, map5[1L])
+        assertEquals(2L, map5[2L])
+        assertEquals(3L, map5[3L])
+        assertEquals(4L, map5[4L])
+        assertEquals(5L, map5[5L])
+    }
+
+    @Test
+    fun mutableLongLongMapInitFunction() {
+        val map1 = mutableLongLongMapOf(
+            1L, 1L,
+        )
+        assertEquals(1, map1.size)
+        assertEquals(1L, map1[1L])
+
+        val map2 = mutableLongLongMapOf(
+            1L, 1L,
+            2L, 2L,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1L, map2[1L])
+        assertEquals(2L, map2[2L])
+
+        val map3 = mutableLongLongMapOf(
+            1L, 1L,
+            2L, 2L,
+            3L, 3L,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1L, map3[1L])
+        assertEquals(2L, map3[2L])
+        assertEquals(3L, map3[3L])
+
+        val map4 = mutableLongLongMapOf(
+            1L, 1L,
+            2L, 2L,
+            3L, 3L,
+            4L, 4L,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1L, map4[1L])
+        assertEquals(2L, map4[2L])
+        assertEquals(3L, map4[3L])
+        assertEquals(4L, map4[4L])
+
+        val map5 = mutableLongLongMapOf(
+            1L, 1L,
+            2L, 2L,
+            3L, 3L,
+            4L, 4L,
+            5L, 5L,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1L, map5[1L])
+        assertEquals(2L, map5[2L])
+        assertEquals(3L, map5[3L])
+        assertEquals(4L, map5[4L])
+        assertEquals(5L, map5[5L])
     }
 
     @Test
@@ -146,38 +245,6 @@
     }
 
     @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
@@ -468,6 +535,43 @@
     }
 
     @Test
+    fun joinToString() {
+        val map = MutableLongLongMap()
+        repeat(5) {
+            map[it.toLong()] = it.toLong()
+        }
+        val order = IntArray(5)
+        var index = 0
+        map.forEach { key, _ ->
+            order[index++] = key.toInt()
+        }
+        assertEquals(
+            "${order[0].toLong()}=${order[0].toLong()}, ${order[1].toLong()}=" +
+            "${order[1].toLong()}, ${order[2].toLong()}=${order[2].toLong()}," +
+            " ${order[3].toLong()}=${order[3].toLong()}, ${order[4].toLong()}=" +
+            "${order[4].toLong()}",
+            map.joinToString()
+        )
+        assertEquals(
+            "x${order[0].toLong()}=${order[0].toLong()}, ${order[1].toLong()}=" +
+            "${order[1].toLong()}, ${order[2].toLong()}=${order[2].toLong()}...",
+            map.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${order[0].toLong()}=${order[0].toLong()}-${order[1].toLong()}=" +
+            "${order[1].toLong()}-${order[2].toLong()}=${order[2].toLong()}-" +
+            "${order[3].toLong()}=${order[3].toLong()}-${order[4].toLong()}=" +
+            "${order[4].toLong()}<",
+            map.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        val names = arrayOf("one", "two", "three", "four", "five")
+        assertEquals(
+            "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+            map.joinToString(limit = 3) { key, _ -> names[key.toInt()] }
+        )
+    }
+
+    @Test
     fun equals() {
         val map = MutableLongLongMap()
         map[1L] = 1L
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/LongObjectMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/LongObjectMapTest.kt
index 1a5fd8bf..67fbc5a 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/LongObjectMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongObjectMapTest.kt
@@ -78,14 +78,113 @@
     }
 
     @Test
-    fun longObjectMapPairsFunction() {
-        val map = mutableLongObjectMapOf(
-            1L to "World",
-            2L to "Monde"
+    fun longObjectMapInitFunction() {
+        val map1 = longObjectMapOf(
+            1L, "World",
         )
-        assertEquals(2, map.size)
-        assertEquals("World", map[1L])
-        assertEquals("Monde", map[2L])
+        assertEquals(1, map1.size)
+        assertEquals("World", map1[1L])
+
+        val map2 = longObjectMapOf(
+            1L, "World",
+            2L, "Monde",
+        )
+        assertEquals(2, map2.size)
+        assertEquals("World", map2[1L])
+        assertEquals("Monde", map2[2L])
+
+        val map3 = longObjectMapOf(
+            1L, "World",
+            2L, "Monde",
+            3L, "Welt",
+        )
+        assertEquals(3, map3.size)
+        assertEquals("World", map3[1L])
+        assertEquals("Monde", map3[2L])
+        assertEquals("Welt", map3[3L])
+
+        val map4 = longObjectMapOf(
+            1L, "World",
+            2L, "Monde",
+            3L, "Welt",
+            4L, "Sekai",
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals("World", map4[1L])
+        assertEquals("Monde", map4[2L])
+        assertEquals("Welt", map4[3L])
+        assertEquals("Sekai", map4[4L])
+
+        val map5 = longObjectMapOf(
+            1L, "World",
+            2L, "Monde",
+            3L, "Welt",
+            4L, "Sekai",
+            5L, "Mondo",
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals("World", map5[1L])
+        assertEquals("Monde", map5[2L])
+        assertEquals("Welt", map5[3L])
+        assertEquals("Sekai", map5[4L])
+        assertEquals("Mondo", map5[5L])
+    }
+
+    @Test
+    fun mutableLongObjectMapInitFunction() {
+        val map1 = mutableLongObjectMapOf(
+            1L, "World",
+        )
+        assertEquals(1, map1.size)
+        assertEquals("World", map1[1L])
+
+        val map2 = mutableLongObjectMapOf(
+            1L, "World",
+            2L, "Monde",
+        )
+        assertEquals(2, map2.size)
+        assertEquals("World", map2[1L])
+        assertEquals("Monde", map2[2L])
+
+        val map3 = mutableLongObjectMapOf(
+            1L, "World",
+            2L, "Monde",
+            3L, "Welt",
+        )
+        assertEquals(3, map3.size)
+        assertEquals("World", map3[1L])
+        assertEquals("Monde", map3[2L])
+        assertEquals("Welt", map3[3L])
+
+        val map4 = mutableLongObjectMapOf(
+            1L, "World",
+            2L, "Monde",
+            3L, "Welt",
+            4L, "Sekai",
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals("World", map4[1L])
+        assertEquals("Monde", map4[2L])
+        assertEquals("Welt", map4[3L])
+        assertEquals("Sekai", map4[4L])
+
+        val map5 = mutableLongObjectMapOf(
+            1L, "World",
+            2L, "Monde",
+            3L, "Welt",
+            4L, "Sekai",
+            5L, "Mondo",
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals("World", map5[1L])
+        assertEquals("Monde", map5[2L])
+        assertEquals("Welt", map5[3L])
+        assertEquals("Sekai", map5[4L])
+        assertEquals("Mondo", map5[5L])
     }
 
     @Test
@@ -153,25 +252,12 @@
     }
 
     @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"))
+        map.putAll(mutableLongObjectMapOf(3L, "Welt", 7L, "Mundo"))
 
         assertEquals(4, map.size)
         assertEquals("Welt", map[3L])
@@ -179,28 +265,9 @@
     }
 
     @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")
+        map += longObjectMapOf(3L, "Welt", 7L, "Mundo")
 
         assertEquals(2, map.size)
         assertEquals("Welt", map[3L])
@@ -520,6 +587,41 @@
     }
 
     @Test
+    fun joinToString() {
+        val map = MutableLongObjectMap<String>()
+        repeat(5) {
+            map[it.toLong()] = it.toString()
+        }
+        val order = IntArray(5)
+        var index = 0
+        map.forEach { key, _ ->
+            order[index++] = key.toInt()
+        }
+        assertEquals(
+            "${order[0].toLong()}=${order[0]}, ${order[1].toLong()}=${order[1]}, " +
+            "${order[2].toLong()}=${order[2]}, ${order[3].toLong()}=${order[3]}, " +
+            "${order[4].toLong()}=${order[4]}",
+            map.joinToString()
+        )
+        assertEquals(
+            "x${order[0].toLong()}=${order[0]}, ${order[1].toLong()}=${order[1]}, " +
+            "${order[2].toLong()}=${order[2]}...",
+            map.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${order[0].toLong()}=${order[0]}-${order[1].toLong()}=${order[1]}-" +
+            "${order[2].toLong()}=${order[2]}-${order[3].toLong()}=${order[3]}-" +
+            "${order[4].toLong()}=${order[4]}<",
+            map.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        val names = arrayOf("one", "two", "three", "four", "five")
+        assertEquals(
+            "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+            map.joinToString(limit = 3) { key, _ -> names[key.toInt()] }
+        )
+    }
+
+    @Test
     fun equals() {
         val map = MutableLongObjectMap<String?>()
         map[1L] = "World"
@@ -626,7 +728,7 @@
         map[5L] = "Mondo"
         map[6L] = "Sesang"
 
-        assertTrue(map.all { key, value -> key < 7L && value.length > 0 })
+        assertTrue(map.all { key, value -> key < 7L && value.isNotEmpty() })
         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 2ee0e96..951539e 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/LongSetTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongSetTest.kt
@@ -347,6 +347,35 @@
     }
 
     @Test
+    fun joinToString() {
+        val set = longSetOf(1L, 2L, 3L, 4L, 5L)
+        val order = IntArray(5)
+        var index = 0
+        set.forEach { element ->
+            order[index++] = element.toInt()
+        }
+        assertEquals(
+            "${order[0].toLong()}, ${order[1].toLong()}, ${order[2].toLong()}, " +
+            "${order[3].toLong()}, ${order[4].toLong()}",
+            set.joinToString()
+        )
+        assertEquals(
+            "x${order[0].toLong()}, ${order[1].toLong()}, ${order[2].toLong()}...",
+            set.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${order[0].toLong()}-${order[1].toLong()}-${order[2].toLong()}-" +
+            "${order[3].toLong()}-${order[4].toLong()}<",
+            set.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        val names = arrayOf("one", "two", "three", "four", "five")
+        assertEquals(
+            "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+            set.joinToString(limit = 3) { names[it.toInt()] }
+        )
+    }
+
+    @Test
     fun equals() {
         val set = MutableLongSet()
         set += 1L
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectFloatMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectFloatMapTest.kt
index 3ae3e6e..98ff5de 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectFloatMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectFloatMapTest.kt
@@ -79,14 +79,113 @@
     }
 
     @Test
-    fun objectFloatMapPairsFunction() {
-        val map = mutableObjectFloatMapOf(
-            "Hello" to 1f,
-            "Bonjour" to 2f
+    fun objectFloatMapInitFunction() {
+        val map1 = objectFloatMapOf(
+            "Hello", 1f,
         )
-        assertEquals(2, map.size)
-        assertEquals(1f, map["Hello"])
-        assertEquals(2f, map["Bonjour"])
+        assertEquals(1, map1.size)
+        assertEquals(1f, map1["Hello"])
+
+        val map2 = objectFloatMapOf(
+            "Hello", 1f,
+            "Bonjour", 2f,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1f, map2["Hello"])
+        assertEquals(2f, map2["Bonjour"])
+
+        val map3 = objectFloatMapOf(
+            "Hello", 1f,
+            "Bonjour", 2f,
+            "Hallo", 3f,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1f, map3["Hello"])
+        assertEquals(2f, map3["Bonjour"])
+        assertEquals(3f, map3["Hallo"])
+
+        val map4 = objectFloatMapOf(
+            "Hello", 1f,
+            "Bonjour", 2f,
+            "Hallo", 3f,
+            "Konnichiwa", 4f,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1f, map4["Hello"])
+        assertEquals(2f, map4["Bonjour"])
+        assertEquals(3f, map4["Hallo"])
+        assertEquals(4f, map4["Konnichiwa"])
+
+        val map5 = objectFloatMapOf(
+            "Hello", 1f,
+            "Bonjour", 2f,
+            "Hallo", 3f,
+            "Konnichiwa", 4f,
+            "Ciao", 5f,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1f, map5["Hello"])
+        assertEquals(2f, map5["Bonjour"])
+        assertEquals(3f, map5["Hallo"])
+        assertEquals(4f, map5["Konnichiwa"])
+        assertEquals(5f, map5["Ciao"])
+    }
+
+    @Test
+    fun mutableObjectFloatMapInitFunction() {
+        val map1 = mutableObjectFloatMapOf(
+            "Hello", 1f,
+        )
+        assertEquals(1, map1.size)
+        assertEquals(1f, map1["Hello"])
+
+        val map2 = mutableObjectFloatMapOf(
+            "Hello", 1f,
+            "Bonjour", 2f,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1f, map2["Hello"])
+        assertEquals(2f, map2["Bonjour"])
+
+        val map3 = mutableObjectFloatMapOf(
+            "Hello", 1f,
+            "Bonjour", 2f,
+            "Hallo", 3f,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1f, map3["Hello"])
+        assertEquals(2f, map3["Bonjour"])
+        assertEquals(3f, map3["Hallo"])
+
+        val map4 = mutableObjectFloatMapOf(
+            "Hello", 1f,
+            "Bonjour", 2f,
+            "Hallo", 3f,
+            "Konnichiwa", 4f,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1f, map4["Hello"])
+        assertEquals(2f, map4["Bonjour"])
+        assertEquals(3f, map4["Hallo"])
+        assertEquals(4f, map4["Konnichiwa"])
+
+        val map5 = mutableObjectFloatMapOf(
+            "Hello", 1f,
+            "Bonjour", 2f,
+            "Hallo", 3f,
+            "Konnichiwa", 4f,
+            "Ciao", 5f,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1f, map5["Hello"])
+        assertEquals(2f, map5["Bonjour"])
+        assertEquals(3f, map5["Hallo"])
+        assertEquals(4f, map5["Konnichiwa"])
+        assertEquals(5f, map5["Ciao"])
     }
 
     @Test
@@ -147,39 +246,6 @@
     }
 
     @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
@@ -487,6 +553,41 @@
     }
 
     @Test
+    fun joinToString() {
+        val map = MutableObjectFloatMap<String?>()
+        repeat(5) {
+            map[it.toString()] = it.toFloat()
+        }
+        val order = IntArray(5)
+        var index = 0
+        map.forEach { _, value ->
+            order[index++] = value.toInt()
+        }
+        assertEquals(
+            "${order[0]}=${order[0].toFloat()}, ${order[1]}=${order[1].toFloat()}, " +
+            "${order[2]}=${order[2].toFloat()}, ${order[3]}=${order[3].toFloat()}, " +
+            "${order[4]}=${order[4].toFloat()}",
+            map.joinToString()
+        )
+        assertEquals(
+            "x${order[0]}=${order[0].toFloat()}, ${order[1]}=${order[1].toFloat()}, " +
+            "${order[2]}=${order[2].toFloat()}...",
+            map.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${order[0]}=${order[0].toFloat()}-${order[1]}=${order[1].toFloat()}-" +
+            "${order[2]}=${order[2].toFloat()}-${order[3]}=${order[3].toFloat()}-" +
+            "${order[4]}=${order[4].toFloat()}<",
+            map.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        val names = arrayOf("one", "two", "three", "four", "five")
+        assertEquals(
+            "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+            map.joinToString(limit = 3) { _, value -> names[value.toInt()] }
+        )
+    }
+
+    @Test
     fun equals() {
         val map = MutableObjectFloatMap<String?>()
         map["Hello"] = 1f
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectIntMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectIntMapTest.kt
index d3bf7c7..73b00eb 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectIntMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectIntMapTest.kt
@@ -79,14 +79,113 @@
     }
 
     @Test
-    fun objectIntMapPairsFunction() {
-        val map = mutableObjectIntMapOf(
-            "Hello" to 1,
-            "Bonjour" to 2
+    fun objectIntMapInitFunction() {
+        val map1 = objectIntMapOf(
+            "Hello", 1,
         )
-        assertEquals(2, map.size)
-        assertEquals(1, map["Hello"])
-        assertEquals(2, map["Bonjour"])
+        assertEquals(1, map1.size)
+        assertEquals(1, map1["Hello"])
+
+        val map2 = objectIntMapOf(
+            "Hello", 1,
+            "Bonjour", 2,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1, map2["Hello"])
+        assertEquals(2, map2["Bonjour"])
+
+        val map3 = objectIntMapOf(
+            "Hello", 1,
+            "Bonjour", 2,
+            "Hallo", 3,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1, map3["Hello"])
+        assertEquals(2, map3["Bonjour"])
+        assertEquals(3, map3["Hallo"])
+
+        val map4 = objectIntMapOf(
+            "Hello", 1,
+            "Bonjour", 2,
+            "Hallo", 3,
+            "Konnichiwa", 4,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1, map4["Hello"])
+        assertEquals(2, map4["Bonjour"])
+        assertEquals(3, map4["Hallo"])
+        assertEquals(4, map4["Konnichiwa"])
+
+        val map5 = objectIntMapOf(
+            "Hello", 1,
+            "Bonjour", 2,
+            "Hallo", 3,
+            "Konnichiwa", 4,
+            "Ciao", 5,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1, map5["Hello"])
+        assertEquals(2, map5["Bonjour"])
+        assertEquals(3, map5["Hallo"])
+        assertEquals(4, map5["Konnichiwa"])
+        assertEquals(5, map5["Ciao"])
+    }
+
+    @Test
+    fun mutableObjectIntMapInitFunction() {
+        val map1 = mutableObjectIntMapOf(
+            "Hello", 1,
+        )
+        assertEquals(1, map1.size)
+        assertEquals(1, map1["Hello"])
+
+        val map2 = mutableObjectIntMapOf(
+            "Hello", 1,
+            "Bonjour", 2,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1, map2["Hello"])
+        assertEquals(2, map2["Bonjour"])
+
+        val map3 = mutableObjectIntMapOf(
+            "Hello", 1,
+            "Bonjour", 2,
+            "Hallo", 3,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1, map3["Hello"])
+        assertEquals(2, map3["Bonjour"])
+        assertEquals(3, map3["Hallo"])
+
+        val map4 = mutableObjectIntMapOf(
+            "Hello", 1,
+            "Bonjour", 2,
+            "Hallo", 3,
+            "Konnichiwa", 4,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1, map4["Hello"])
+        assertEquals(2, map4["Bonjour"])
+        assertEquals(3, map4["Hallo"])
+        assertEquals(4, map4["Konnichiwa"])
+
+        val map5 = mutableObjectIntMapOf(
+            "Hello", 1,
+            "Bonjour", 2,
+            "Hallo", 3,
+            "Konnichiwa", 4,
+            "Ciao", 5,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1, map5["Hello"])
+        assertEquals(2, map5["Bonjour"])
+        assertEquals(3, map5["Hallo"])
+        assertEquals(4, map5["Konnichiwa"])
+        assertEquals(5, map5["Ciao"])
     }
 
     @Test
@@ -147,39 +246,6 @@
     }
 
     @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
@@ -487,6 +553,41 @@
     }
 
     @Test
+    fun joinToString() {
+        val map = MutableObjectIntMap<String?>()
+        repeat(5) {
+            map[it.toString()] = it.toInt()
+        }
+        val order = IntArray(5)
+        var index = 0
+        map.forEach { _, value ->
+            order[index++] = value.toInt()
+        }
+        assertEquals(
+            "${order[0]}=${order[0].toInt()}, ${order[1]}=${order[1].toInt()}, " +
+            "${order[2]}=${order[2].toInt()}, ${order[3]}=${order[3].toInt()}, " +
+            "${order[4]}=${order[4].toInt()}",
+            map.joinToString()
+        )
+        assertEquals(
+            "x${order[0]}=${order[0].toInt()}, ${order[1]}=${order[1].toInt()}, " +
+            "${order[2]}=${order[2].toInt()}...",
+            map.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${order[0]}=${order[0].toInt()}-${order[1]}=${order[1].toInt()}-" +
+            "${order[2]}=${order[2].toInt()}-${order[3]}=${order[3].toInt()}-" +
+            "${order[4]}=${order[4].toInt()}<",
+            map.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        val names = arrayOf("one", "two", "three", "four", "five")
+        assertEquals(
+            "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+            map.joinToString(limit = 3) { _, value -> names[value.toInt()] }
+        )
+    }
+
+    @Test
     fun equals() {
         val map = MutableObjectIntMap<String?>()
         map["Hello"] = 1
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectListTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectListTest.kt
index d524dcf..37752f6 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectListTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectListTest.kt
@@ -83,6 +83,30 @@
     fun string() {
         assertEquals("[1, 2, 3, 4, 5]", list.toString())
         assertEquals("[]", mutableObjectListOf<Int>().toString())
+        val weirdList = MutableObjectList<Any>()
+        weirdList.add(weirdList)
+        assertEquals("[(this)]", weirdList.toString())
+    }
+
+    @Test
+    fun joinToString() {
+        assertEquals("1, 2, 3, 4, 5", list.joinToString())
+        assertEquals(
+            "x1, 2, 3...",
+            list.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">1-2-3-4-5<",
+            list.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        assertEquals("one, two, three...", list.joinToString(limit = 3) {
+            when (it) {
+                1 -> "one"
+                2 -> "two"
+                3 -> "three"
+                else -> "whoops"
+            }
+        })
     }
 
     @Test
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectLongMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectLongMapTest.kt
index 3cf8c73..015b5a8 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectLongMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectLongMapTest.kt
@@ -79,14 +79,113 @@
     }
 
     @Test
-    fun objectLongMapPairsFunction() {
-        val map = mutableObjectLongMapOf(
-            "Hello" to 1L,
-            "Bonjour" to 2L
+    fun objectLongMapInitFunction() {
+        val map1 = objectLongMapOf(
+            "Hello", 1L,
         )
-        assertEquals(2, map.size)
-        assertEquals(1L, map["Hello"])
-        assertEquals(2L, map["Bonjour"])
+        assertEquals(1, map1.size)
+        assertEquals(1L, map1["Hello"])
+
+        val map2 = objectLongMapOf(
+            "Hello", 1L,
+            "Bonjour", 2L,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1L, map2["Hello"])
+        assertEquals(2L, map2["Bonjour"])
+
+        val map3 = objectLongMapOf(
+            "Hello", 1L,
+            "Bonjour", 2L,
+            "Hallo", 3L,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1L, map3["Hello"])
+        assertEquals(2L, map3["Bonjour"])
+        assertEquals(3L, map3["Hallo"])
+
+        val map4 = objectLongMapOf(
+            "Hello", 1L,
+            "Bonjour", 2L,
+            "Hallo", 3L,
+            "Konnichiwa", 4L,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1L, map4["Hello"])
+        assertEquals(2L, map4["Bonjour"])
+        assertEquals(3L, map4["Hallo"])
+        assertEquals(4L, map4["Konnichiwa"])
+
+        val map5 = objectLongMapOf(
+            "Hello", 1L,
+            "Bonjour", 2L,
+            "Hallo", 3L,
+            "Konnichiwa", 4L,
+            "Ciao", 5L,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1L, map5["Hello"])
+        assertEquals(2L, map5["Bonjour"])
+        assertEquals(3L, map5["Hallo"])
+        assertEquals(4L, map5["Konnichiwa"])
+        assertEquals(5L, map5["Ciao"])
+    }
+
+    @Test
+    fun mutableObjectLongMapInitFunction() {
+        val map1 = mutableObjectLongMapOf(
+            "Hello", 1L,
+        )
+        assertEquals(1, map1.size)
+        assertEquals(1L, map1["Hello"])
+
+        val map2 = mutableObjectLongMapOf(
+            "Hello", 1L,
+            "Bonjour", 2L,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1L, map2["Hello"])
+        assertEquals(2L, map2["Bonjour"])
+
+        val map3 = mutableObjectLongMapOf(
+            "Hello", 1L,
+            "Bonjour", 2L,
+            "Hallo", 3L,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1L, map3["Hello"])
+        assertEquals(2L, map3["Bonjour"])
+        assertEquals(3L, map3["Hallo"])
+
+        val map4 = mutableObjectLongMapOf(
+            "Hello", 1L,
+            "Bonjour", 2L,
+            "Hallo", 3L,
+            "Konnichiwa", 4L,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1L, map4["Hello"])
+        assertEquals(2L, map4["Bonjour"])
+        assertEquals(3L, map4["Hallo"])
+        assertEquals(4L, map4["Konnichiwa"])
+
+        val map5 = mutableObjectLongMapOf(
+            "Hello", 1L,
+            "Bonjour", 2L,
+            "Hallo", 3L,
+            "Konnichiwa", 4L,
+            "Ciao", 5L,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1L, map5["Hello"])
+        assertEquals(2L, map5["Bonjour"])
+        assertEquals(3L, map5["Hallo"])
+        assertEquals(4L, map5["Konnichiwa"])
+        assertEquals(5L, map5["Ciao"])
     }
 
     @Test
@@ -147,39 +246,6 @@
     }
 
     @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
@@ -487,6 +553,41 @@
     }
 
     @Test
+    fun joinToString() {
+        val map = MutableObjectLongMap<String?>()
+        repeat(5) {
+            map[it.toString()] = it.toLong()
+        }
+        val order = IntArray(5)
+        var index = 0
+        map.forEach { _, value ->
+            order[index++] = value.toInt()
+        }
+        assertEquals(
+            "${order[0]}=${order[0].toLong()}, ${order[1]}=${order[1].toLong()}, " +
+            "${order[2]}=${order[2].toLong()}, ${order[3]}=${order[3].toLong()}, " +
+            "${order[4]}=${order[4].toLong()}",
+            map.joinToString()
+        )
+        assertEquals(
+            "x${order[0]}=${order[0].toLong()}, ${order[1]}=${order[1].toLong()}, " +
+            "${order[2]}=${order[2].toLong()}...",
+            map.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${order[0]}=${order[0].toLong()}-${order[1]}=${order[1].toLong()}-" +
+            "${order[2]}=${order[2].toLong()}-${order[3]}=${order[3].toLong()}-" +
+            "${order[4]}=${order[4].toLong()}<",
+            map.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        val names = arrayOf("one", "two", "three", "four", "five")
+        assertEquals(
+            "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+            map.joinToString(limit = 3) { _, value -> names[value.toInt()] }
+        )
+    }
+
+    @Test
     fun equals() {
         val map = MutableObjectLongMap<String?>()
         map["Hello"] = 1L
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/PairTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/PairTest.kt
index b132651..51cc5963 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/PairTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/PairTest.kt
@@ -24,18 +24,18 @@
 
     @Test
     fun intCreation() {
-        val pair = PairIntInt(3, 5)
+        val pair = IntIntPair(3, 5)
         assertEquals(3, pair.first)
         assertEquals(5, pair.second)
     }
 
     @Test
     fun intEquality() {
-        val pair = PairIntInt(3, 5)
-        val pairEqual = PairIntInt(3, 5)
-        val pairUnequal1 = PairIntInt(4, 5)
-        val pairUnequal2 = PairIntInt(3, 6)
-        val pairUnequal3 = PairIntInt(4, 6)
+        val pair = IntIntPair(3, 5)
+        val pairEqual = IntIntPair(3, 5)
+        val pairUnequal1 = IntIntPair(4, 5)
+        val pairUnequal2 = IntIntPair(3, 6)
+        val pairUnequal3 = IntIntPair(4, 6)
 
         assertEquals(pair, pairEqual)
         assertNotEquals(pair, pairUnequal1)
@@ -45,7 +45,7 @@
 
     @Test
     fun intDestructing() {
-        val pair = PairIntInt(3, 5)
+        val pair = IntIntPair(3, 5)
         val (first, second) = pair
         assertEquals(3, first)
         assertEquals(5, second)
@@ -53,18 +53,18 @@
 
     @Test
     fun floatCreation() {
-        val pair = PairFloatFloat(3f, 5f)
+        val pair = FloatFloatPair(3f, 5f)
         assertEquals(3f, pair.first)
         assertEquals(5f, pair.second)
     }
 
     @Test
     fun floatEquality() {
-        val pair = PairFloatFloat(3f, 5f)
-        val pairEqual = PairFloatFloat(3f, 5f)
-        val pairUnequal1 = PairFloatFloat(4f, 5f)
-        val pairUnequal2 = PairFloatFloat(3f, 6f)
-        val pairUnequal3 = PairFloatFloat(4f, 6f)
+        val pair = FloatFloatPair(3f, 5f)
+        val pairEqual = FloatFloatPair(3f, 5f)
+        val pairUnequal1 = FloatFloatPair(4f, 5f)
+        val pairUnequal2 = FloatFloatPair(3f, 6f)
+        val pairUnequal3 = FloatFloatPair(4f, 6f)
 
         assertEquals(pair, pairEqual)
         assertNotEquals(pair, pairUnequal1)
@@ -74,7 +74,7 @@
 
     @Test
     fun floatDestructing() {
-        val pair = PairFloatFloat(3f, 5f)
+        val pair = FloatFloatPair(3f, 5f)
         val (first, second) = pair
         assertEquals(3f, first)
         assertEquals(5f, second)
@@ -82,18 +82,18 @@
 
     @Test
     fun longCreation() {
-        val pair = PairLongLong(3, 5)
+        val pair = LongLongPair(3, 5)
         assertEquals(3, pair.first)
         assertEquals(5, pair.second)
     }
 
     @Test
     fun longEquality() {
-        val pair = PairLongLong(3, 5)
-        val pairEqual = PairLongLong(3, 5)
-        val pairUnequal1 = PairLongLong(4, 5)
-        val pairUnequal2 = PairLongLong(3, 6)
-        val pairUnequal3 = PairLongLong(4, 6)
+        val pair = LongLongPair(3, 5)
+        val pairEqual = LongLongPair(3, 5)
+        val pairUnequal1 = LongLongPair(4, 5)
+        val pairUnequal2 = LongLongPair(3, 6)
+        val pairUnequal3 = LongLongPair(4, 6)
 
         assertEquals(pair, pairEqual)
         assertNotEquals(pair, pairUnequal1)
@@ -103,7 +103,7 @@
 
     @Test
     fun longDestructing() {
-        val pair = PairLongLong(3, 5)
+        val pair = LongLongPair(3, 5)
         val (first, second) = pair
         assertEquals(3L, first)
         assertEquals(5L, second)
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterMapTest.kt
index a869735..ec03f0d 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterMapTest.kt
@@ -454,6 +454,34 @@
     }
 
     @Test
+    fun minusScatterSet() {
+        val map = MutableScatterMap<String, String>()
+        map["Hello"] = "World"
+        map["Bonjour"] = "Monde"
+        map["Hallo"] = "Welt"
+
+        map -= scatterSetOf("Hallo", "Bonjour")
+
+        assertEquals(1, map.size)
+        assertNull(map["Hallo"])
+        assertNull(map["Bonjour"])
+    }
+
+    @Test
+    fun minusObjectList() {
+        val map = MutableScatterMap<String, String>()
+        map["Hello"] = "World"
+        map["Bonjour"] = "Monde"
+        map["Hallo"] = "Welt"
+
+        map -= objectListOf("Hallo", "Bonjour")
+
+        assertEquals(1, map.size)
+        assertNull(map["Hallo"])
+        assertNull(map["Bonjour"])
+    }
+
+    @Test
     fun conditionalRemove() {
         val map = MutableScatterMap<String?, String?>()
         assertFalse(map.remove("Hello", "World"))
@@ -590,6 +618,38 @@
     }
 
     @Test
+    fun joinToString() {
+        val map = mutableScatterMapOf(1 to 1f, 2 to 2f, 3 to 3f, 4 to 4f, 5 to 5f)
+        val order = IntArray(5)
+        var index = 0
+        map.forEach { key, _ ->
+            order[index++] = key
+        }
+        assertEquals(
+            "${order[0]}=${order[0].toFloat()}, ${order[1]}=${order[1].toFloat()}, " +
+                "${order[2]}=${order[2].toFloat()}, ${order[3]}=${order[3].toFloat()}, " +
+                "${order[4]}=${order[4].toFloat()}",
+            map.joinToString()
+        )
+        assertEquals(
+            "x${order[0]}=${order[0].toFloat()}, ${order[1]}=${order[1].toFloat()}, " +
+                "${order[2]}=${order[2].toFloat()}...",
+            map.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${order[0]}=${order[0].toFloat()}-${order[1]}=${order[1].toFloat()}-" +
+                "${order[2]}=${order[2].toFloat()}-${order[3]}=${order[3].toFloat()}-" +
+                "${order[4]}=${order[4].toFloat()}<",
+            map.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        val names = arrayOf("one", "two", "three", "four", "five")
+        assertEquals(
+            "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+            map.joinToString(limit = 3) { key, _ -> names[key] }
+        )
+    }
+
+    @Test
     fun equals() {
         val map = MutableScatterMap<String?, String?>()
         map["Hello"] = "World"
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterSetTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterSetTest.kt
index 5a4ba52..824502d 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterSetTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ScatterSetTest.kt
@@ -144,6 +144,16 @@
     }
 
     @Test
+    fun addAllObjectList() {
+        val set = mutableScatterSetOf("Hello")
+        assertFalse(set.addAll(objectListOf("Hello")))
+        assertEquals(1, set.size)
+        assertTrue(set.addAll(objectListOf("Hello", "World")))
+        assertEquals(2, set.size)
+        assertTrue("World" in set)
+    }
+
+    @Test
     fun plusAssignArray() {
         val set = mutableScatterSetOf("Hello")
         set += arrayOf("Hello")
@@ -184,6 +194,16 @@
     }
 
     @Test
+    fun plusAssignObjectList() {
+        val set = mutableScatterSetOf("Hello")
+        set += objectListOf("Hello")
+        assertEquals(1, set.size)
+        set += objectListOf("Hello", "World")
+        assertEquals(2, set.size)
+        assertTrue("World" in set)
+    }
+
+    @Test
     fun nullElement() {
         val set = MutableScatterSet<String?>()
         set += null
@@ -348,6 +368,16 @@
     }
 
     @Test
+    fun removeAllObjectList() {
+        val set = mutableScatterSetOf("Hello", "World")
+        assertFalse(set.removeAll(objectListOf("Hola", "Bonjour")))
+        assertEquals(2, set.size)
+        assertTrue(set.removeAll(objectListOf("Hola", "Hello", "Bonjour")))
+        assertEquals(1, set.size)
+        assertFalse("Hello" in set)
+    }
+
+    @Test
     fun minusAssignArray() {
         val set = mutableScatterSetOf("Hello", "World")
         set -= arrayOf("Hola", "Bonjour")
@@ -388,6 +418,16 @@
     }
 
     @Test
+    fun minusAssignObjectList() {
+        val set = mutableScatterSetOf("Hello", "World")
+        set -= objectListOf("Hola", "Bonjour")
+        assertEquals(2, set.size)
+        set -= objectListOf("Hola", "Hello", "Bonjour")
+        assertEquals(1, set.size)
+        assertFalse("Hello" in set)
+    }
+
+    @Test
     fun insertManyEntries() {
         val set = MutableScatterSet<String>()
 
@@ -461,6 +501,33 @@
     }
 
     @Test
+    fun joinToString() {
+        val set = scatterSetOf(1, 2, 3, 4, 5)
+        val order = IntArray(5)
+        var index = 0
+        set.forEach { element ->
+            order[index++] = element
+        }
+        assertEquals(
+            "${order[0]}, ${order[1]}, ${order[2]}, ${order[3]}, ${order[4]}",
+            set.joinToString()
+        )
+        assertEquals(
+            "x${order[0]}, ${order[1]}, ${order[2]}...",
+            set.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${order[0]}-${order[1]}-${order[2]}-${order[3]}-${order[4]}<",
+            set.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        val names = arrayOf("one", "two", "three", "four", "five")
+        assertEquals(
+            "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+            set.joinToString(limit = 3) { names[it] }
+        )
+    }
+
+    @Test
     fun hashCodeAddValues() {
         val set = mutableScatterSetOf<String?>()
         assertEquals(0, set.hashCode())
diff --git a/collection/collection/template/ObjectPValueMap.kt.template b/collection/collection/template/ObjectPValueMap.kt.template
index ba8284e..b9f15a4 100644
--- a/collection/collection/template/ObjectPValueMap.kt.template
+++ b/collection/collection/template/ObjectPValueMap.kt.template
@@ -23,6 +23,7 @@
 
 import androidx.collection.internal.EMPTY_OBJECTS
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -54,38 +55,186 @@
     EmptyObjectPValueMap as ObjectPValueMap<K>
 
 /**
- * Returns a new [MutableObjectPValueMap].
+ * Returns a new [ObjectPValueMap] with only [key1] associated with [value1].
+ */
+public fun <K> objectPValueMapOf(
+    key1: K,
+    value1: PValue
+): ObjectPValueMap<K> =
+    MutableObjectPValueMap<K>().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [ObjectPValueMap] with only [key1] and [key2] associated with
+ * [value1] and [value2], respectively.
+ */
+public fun <K> objectPValueMapOf(
+    key1: K,
+    value1: PValue,
+    key2: K,
+    value2: PValue,
+): ObjectPValueMap<K> =
+    MutableObjectPValueMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [ObjectPValueMap] with only [key1], [key2], and [key3] associated with
+ * [value1], [value2], and [value3], respectively.
+ */
+public fun <K> objectPValueMapOf(
+    key1: K,
+    value1: PValue,
+    key2: K,
+    value2: PValue,
+    key3: K,
+    value3: PValue,
+): ObjectPValueMap<K> =
+    MutableObjectPValueMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [ObjectPValueMap] with only [key1], [key2], [key3], and [key4] associated with
+ * [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun <K> objectPValueMapOf(
+    key1: K,
+    value1: PValue,
+    key2: K,
+    value2: PValue,
+    key3: K,
+    value3: PValue,
+    key4: K,
+    value4: PValue,
+): ObjectPValueMap<K> =
+    MutableObjectPValueMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [ObjectPValueMap] with only [key1], [key2], [key3], [key4], and [key5] associated
+ * with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun <K> objectPValueMapOf(
+    key1: K,
+    value1: PValue,
+    key2: K,
+    value2: PValue,
+    key3: K,
+    value3: PValue,
+    key4: K,
+    value4: PValue,
+    key5: K,
+    value5: PValue,
+): ObjectPValueMap<K> =
+    MutableObjectPValueMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
+    }
+
+/**
+ * Returns a new empty [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.
+ * Returns a new [MutableObjectPValueMap] with only [key1] associated with [value1].
  */
-public fun <K> objectPValueMapOf(vararg pairs: Pair<K, PValue>): ObjectPValueMap<K> =
-    MutableObjectPValueMap<K>(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun <K> mutableObjectPValueMapOf(
+    key1: K,
+    value1: PValue,
+): MutableObjectPValueMap<K> =
+    MutableObjectPValueMap<K>().also { map ->
+        map[key1] = value1
     }
 
 /**
- * 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.
+ * Returns a new [MutableObjectPValueMap] with only [key1] and [key2] associated with
+ * [value1] and [value2], respectively.
  */
-public fun <K> mutableObjectPValueMapOf(vararg pairs: Pair<K, PValue>): MutableObjectPValueMap<K> =
-    MutableObjectPValueMap<K>(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun <K> mutableObjectPValueMapOf(
+    key1: K,
+    value1: PValue,
+    key2: K,
+    value2: PValue,
+): MutableObjectPValueMap<K> =
+    MutableObjectPValueMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [MutableObjectPValueMap] with only [key1], [key2], and [key3] associated with
+ * [value1], [value2], and [value3], respectively.
+ */
+public fun <K> mutableObjectPValueMapOf(
+    key1: K,
+    value1: PValue,
+    key2: K,
+    value2: PValue,
+    key3: K,
+    value3: PValue,
+): MutableObjectPValueMap<K> =
+    MutableObjectPValueMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [MutableObjectPValueMap] with only [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun <K> mutableObjectPValueMapOf(
+    key1: K,
+    value1: PValue,
+    key2: K,
+    value2: PValue,
+    key3: K,
+    value3: PValue,
+    key4: K,
+    value4: PValue,
+): MutableObjectPValueMap<K> =
+    MutableObjectPValueMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [MutableObjectPValueMap] with only [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun <K> mutableObjectPValueMapOf(
+    key1: K,
+    value1: PValue,
+    key2: K,
+    value2: PValue,
+    key3: K,
+    value3: PValue,
+    key4: K,
+    value4: PValue,
+    key5: K,
+    value5: PValue,
+): MutableObjectPValueMap<K> =
+    MutableObjectPValueMap<K>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -343,6 +492,73 @@
     }
 
     /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@ObjectPValueMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(key)
+            append('=')
+            append(value)
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. Each entry is created with [transform].
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (key: K, value: PValue) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@ObjectPValueMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(key, value))
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
      * Returns the hash code value for this map. The hash code the sum of the hash
      * codes of each key/value pair.
      */
@@ -560,20 +776,6 @@
     }
 
     /**
-     * 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>) {
@@ -583,29 +785,6 @@
     }
 
     /**
-     * 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)
diff --git a/collection/collection/template/ObjectPValueMapTest.kt.template b/collection/collection/template/ObjectPValueMapTest.kt.template
index d2c6f43..50daf3f 100644
--- a/collection/collection/template/ObjectPValueMapTest.kt.template
+++ b/collection/collection/template/ObjectPValueMapTest.kt.template
@@ -79,14 +79,113 @@
     }
 
     @Test
-    fun objectPValueMapPairsFunction() {
-        val map = mutableObjectPValueMapOf(
-            "Hello" to 1ValueSuffix,
-            "Bonjour" to 2ValueSuffix
+    fun objectPValueMapInitFunction() {
+        val map1 = objectPValueMapOf(
+            "Hello", 1ValueSuffix,
         )
-        assertEquals(2, map.size)
-        assertEquals(1ValueSuffix, map["Hello"])
-        assertEquals(2ValueSuffix, map["Bonjour"])
+        assertEquals(1, map1.size)
+        assertEquals(1ValueSuffix, map1["Hello"])
+
+        val map2 = objectPValueMapOf(
+            "Hello", 1ValueSuffix,
+            "Bonjour", 2ValueSuffix,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1ValueSuffix, map2["Hello"])
+        assertEquals(2ValueSuffix, map2["Bonjour"])
+
+        val map3 = objectPValueMapOf(
+            "Hello", 1ValueSuffix,
+            "Bonjour", 2ValueSuffix,
+            "Hallo", 3ValueSuffix,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1ValueSuffix, map3["Hello"])
+        assertEquals(2ValueSuffix, map3["Bonjour"])
+        assertEquals(3ValueSuffix, map3["Hallo"])
+
+        val map4 = objectPValueMapOf(
+            "Hello", 1ValueSuffix,
+            "Bonjour", 2ValueSuffix,
+            "Hallo", 3ValueSuffix,
+            "Konnichiwa", 4ValueSuffix,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1ValueSuffix, map4["Hello"])
+        assertEquals(2ValueSuffix, map4["Bonjour"])
+        assertEquals(3ValueSuffix, map4["Hallo"])
+        assertEquals(4ValueSuffix, map4["Konnichiwa"])
+
+        val map5 = objectPValueMapOf(
+            "Hello", 1ValueSuffix,
+            "Bonjour", 2ValueSuffix,
+            "Hallo", 3ValueSuffix,
+            "Konnichiwa", 4ValueSuffix,
+            "Ciao", 5ValueSuffix,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1ValueSuffix, map5["Hello"])
+        assertEquals(2ValueSuffix, map5["Bonjour"])
+        assertEquals(3ValueSuffix, map5["Hallo"])
+        assertEquals(4ValueSuffix, map5["Konnichiwa"])
+        assertEquals(5ValueSuffix, map5["Ciao"])
+    }
+
+    @Test
+    fun mutableObjectPValueMapInitFunction() {
+        val map1 = mutableObjectPValueMapOf(
+            "Hello", 1ValueSuffix,
+        )
+        assertEquals(1, map1.size)
+        assertEquals(1ValueSuffix, map1["Hello"])
+
+        val map2 = mutableObjectPValueMapOf(
+            "Hello", 1ValueSuffix,
+            "Bonjour", 2ValueSuffix,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1ValueSuffix, map2["Hello"])
+        assertEquals(2ValueSuffix, map2["Bonjour"])
+
+        val map3 = mutableObjectPValueMapOf(
+            "Hello", 1ValueSuffix,
+            "Bonjour", 2ValueSuffix,
+            "Hallo", 3ValueSuffix,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1ValueSuffix, map3["Hello"])
+        assertEquals(2ValueSuffix, map3["Bonjour"])
+        assertEquals(3ValueSuffix, map3["Hallo"])
+
+        val map4 = mutableObjectPValueMapOf(
+            "Hello", 1ValueSuffix,
+            "Bonjour", 2ValueSuffix,
+            "Hallo", 3ValueSuffix,
+            "Konnichiwa", 4ValueSuffix,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1ValueSuffix, map4["Hello"])
+        assertEquals(2ValueSuffix, map4["Bonjour"])
+        assertEquals(3ValueSuffix, map4["Hallo"])
+        assertEquals(4ValueSuffix, map4["Konnichiwa"])
+
+        val map5 = mutableObjectPValueMapOf(
+            "Hello", 1ValueSuffix,
+            "Bonjour", 2ValueSuffix,
+            "Hallo", 3ValueSuffix,
+            "Konnichiwa", 4ValueSuffix,
+            "Ciao", 5ValueSuffix,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1ValueSuffix, map5["Hello"])
+        assertEquals(2ValueSuffix, map5["Bonjour"])
+        assertEquals(3ValueSuffix, map5["Hallo"])
+        assertEquals(4ValueSuffix, map5["Konnichiwa"])
+        assertEquals(5ValueSuffix, map5["Ciao"])
     }
 
     @Test
@@ -147,39 +246,6 @@
     }
 
     @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
@@ -487,6 +553,41 @@
     }
 
     @Test
+    fun joinToString() {
+        val map = MutableObjectPValueMap<String?>()
+        repeat(5) {
+            map[it.toString()] = it.toPValue()
+        }
+        val order = IntArray(5)
+        var index = 0
+        map.forEach { _, value ->
+            order[index++] = value.toInt()
+        }
+        assertEquals(
+            "${order[0]}=${order[0].toPValue()}, ${order[1]}=${order[1].toPValue()}, " +
+            "${order[2]}=${order[2].toPValue()}, ${order[3]}=${order[3].toPValue()}, " +
+            "${order[4]}=${order[4].toPValue()}",
+            map.joinToString()
+        )
+        assertEquals(
+            "x${order[0]}=${order[0].toPValue()}, ${order[1]}=${order[1].toPValue()}, " +
+            "${order[2]}=${order[2].toPValue()}...",
+            map.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${order[0]}=${order[0].toPValue()}-${order[1]}=${order[1].toPValue()}-" +
+            "${order[2]}=${order[2].toPValue()}-${order[3]}=${order[3].toPValue()}-" +
+            "${order[4]}=${order[4].toPValue()}<",
+            map.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        val names = arrayOf("one", "two", "three", "four", "five")
+        assertEquals(
+            "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+            map.joinToString(limit = 3) { _, value -> names[value.toInt()] }
+        )
+    }
+
+    @Test
     fun equals() {
         val map = MutableObjectPValueMap<String?>()
         map["Hello"] = 1ValueSuffix
diff --git a/collection/collection/template/PKeyList.kt.template b/collection/collection/template/PKeyList.kt.template
index 620d2e4..1bf836e 100644
--- a/collection/collection/template/PKeyList.kt.template
+++ b/collection/collection/template/PKeyList.kt.template
@@ -21,6 +21,7 @@
 import kotlin.contracts.ExperimentalContracts
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -425,6 +426,67 @@
     }
 
     /**
+     * Creates a String from the elements separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        this@PKeyList.forEachIndexed { index, element ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(element)
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the elements separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. [transform] dictates how each element will be represented.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (PKey) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        this@PKeyList.forEachIndexed { index, element ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(element))
+        }
+        append(postfix)
+    }
+
+    /**
      * Returns a hash code based on the contents of the [PKeyList].
      */
     override fun hashCode(): Int {
@@ -457,23 +519,7 @@
      * 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(']')
-        }
-    }
+    override fun toString(): String = joinToString(prefix = "[", postfix = "]")
 }
 
 /**
diff --git a/collection/collection/template/PKeyListTest.kt.template b/collection/collection/template/PKeyListTest.kt.template
index 34915e5..cd430c9 100644
--- a/collection/collection/template/PKeyListTest.kt.template
+++ b/collection/collection/template/PKeyListTest.kt.template
@@ -93,6 +93,27 @@
     }
 
     @Test
+    fun joinToString() {
+        assertEquals("${1KeySuffix}, ${2KeySuffix}, ${3KeySuffix}, ${4KeySuffix}, ${5KeySuffix}", list.joinToString())
+        assertEquals(
+            "x${1KeySuffix}, ${2KeySuffix}, ${3KeySuffix}...",
+            list.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${1KeySuffix}-${2KeySuffix}-${3KeySuffix}-${4KeySuffix}-${5KeySuffix}<",
+            list.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        assertEquals("one, two, three...", list.joinToString(limit = 3) {
+            when (it.toInt()) {
+                1 -> "one"
+                2 -> "two"
+                3 -> "three"
+                else -> "whoops"
+            }
+        })
+    }
+
+    @Test
     fun size() {
         assertEquals(5, list.size)
         assertEquals(5, list.count())
diff --git a/collection/collection/template/PKeyObjectMap.kt.template b/collection/collection/template/PKeyObjectMap.kt.template
index 2bb53a2b..5908308 100644
--- a/collection/collection/template/PKeyObjectMap.kt.template
+++ b/collection/collection/template/PKeyObjectMap.kt.template
@@ -23,6 +23,7 @@
 
 import androidx.collection.internal.EMPTY_OBJECTS
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -52,18 +53,87 @@
 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.
+ * Returns a new [PKeyObjectMap] with [key1] associated with [value1].
  */
-public fun <V> pKeyObjectMapOf(vararg pairs: Pair<PKey, V>): PKeyObjectMap<V> =
-    MutablePKeyObjectMap<V>(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun <V> pKeyObjectMapOf(
+    key1: PKey,
+    value1: V
+): PKeyObjectMap<V> = MutablePKeyObjectMap<V>().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [PKeyObjectMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun <V> pKeyObjectMapOf(
+    key1: PKey,
+    value1: V,
+    key2: PKey,
+    value2: V,
+): PKeyObjectMap<V> = MutablePKeyObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [PKeyObjectMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun <V> pKeyObjectMapOf(
+    key1: PKey,
+    value1: V,
+    key2: PKey,
+    value2: V,
+    key3: PKey,
+    value3: V,
+): PKeyObjectMap<V> = MutablePKeyObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [PKeyObjectMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun <V> pKeyObjectMapOf(
+    key1: PKey,
+    value1: V,
+    key2: PKey,
+    value2: V,
+    key3: PKey,
+    value3: V,
+    key4: PKey,
+    value4: V,
+): PKeyObjectMap<V> = MutablePKeyObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [PKeyObjectMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun <V> pKeyObjectMapOf(
+    key1: PKey,
+    value1: V,
+    key2: PKey,
+    value2: V,
+    key3: PKey,
+    value3: V,
+    key4: PKey,
+    value4: V,
+    key5: PKey,
+    value5: V,
+): PKeyObjectMap<V> = MutablePKeyObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -72,18 +142,87 @@
 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.
+ * Returns a new [MutablePKeyObjectMap] with [key1] associated with [value1].
  */
-public fun <V> mutablePKeyObjectMapOf(vararg pairs: Pair<PKey, V>): MutablePKeyObjectMap<V> =
-    MutablePKeyObjectMap<V>(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun <V> mutablePKeyObjectMapOf(
+    key1: PKey,
+    value1: V
+): MutablePKeyObjectMap<V> = MutablePKeyObjectMap<V>().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [MutablePKeyObjectMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun <V> mutablePKeyObjectMapOf(
+    key1: PKey,
+    value1: V,
+    key2: PKey,
+    value2: V,
+): MutablePKeyObjectMap<V> = MutablePKeyObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [MutablePKeyObjectMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun <V> mutablePKeyObjectMapOf(
+    key1: PKey,
+    value1: V,
+    key2: PKey,
+    value2: V,
+    key3: PKey,
+    value3: V,
+): MutablePKeyObjectMap<V> = MutablePKeyObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [MutablePKeyObjectMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun <V> mutablePKeyObjectMapOf(
+    key1: PKey,
+    value1: V,
+    key2: PKey,
+    value2: V,
+    key3: PKey,
+    value3: V,
+    key4: PKey,
+    value4: V,
+): MutablePKeyObjectMap<V> = MutablePKeyObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [MutablePKeyObjectMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun <V> mutablePKeyObjectMapOf(
+    key1: PKey,
+    value1: V,
+    key2: PKey,
+    value2: V,
+    key3: PKey,
+    value3: V,
+    key4: PKey,
+    value4: V,
+    key5: PKey,
+    value5: V,
+): MutablePKeyObjectMap<V> = MutablePKeyObjectMap<V>().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -334,6 +473,73 @@
     }
 
     /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@PKeyObjectMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(key)
+            append('=')
+            append(value)
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. Each entry is created with [transform].
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (key: PKey, value: V) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@PKeyObjectMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(key, value))
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
      * Returns the hash code value for this map. The hash code the sum of the hash
      * codes of each key/value pair.
      */
@@ -554,20 +760,6 @@
     }
 
     /**
-     * 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>) {
@@ -577,29 +769,6 @@
     }
 
     /**
-     * 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)
diff --git a/collection/collection/template/PKeyObjectMapTest.kt.template b/collection/collection/template/PKeyObjectMapTest.kt.template
index d04a330..817e459 100644
--- a/collection/collection/template/PKeyObjectMapTest.kt.template
+++ b/collection/collection/template/PKeyObjectMapTest.kt.template
@@ -78,14 +78,113 @@
     }
 
     @Test
-    fun pKeyObjectMapPairsFunction() {
-        val map = mutablePKeyObjectMapOf(
-            1KeySuffix to "World",
-            2KeySuffix to "Monde"
+    fun pKeyObjectMapInitFunction() {
+        val map1 = pKeyObjectMapOf(
+            1KeySuffix, "World",
         )
-        assertEquals(2, map.size)
-        assertEquals("World", map[1KeySuffix])
-        assertEquals("Monde", map[2KeySuffix])
+        assertEquals(1, map1.size)
+        assertEquals("World", map1[1KeySuffix])
+
+        val map2 = pKeyObjectMapOf(
+            1KeySuffix, "World",
+            2KeySuffix, "Monde",
+        )
+        assertEquals(2, map2.size)
+        assertEquals("World", map2[1KeySuffix])
+        assertEquals("Monde", map2[2KeySuffix])
+
+        val map3 = pKeyObjectMapOf(
+            1KeySuffix, "World",
+            2KeySuffix, "Monde",
+            3KeySuffix, "Welt",
+        )
+        assertEquals(3, map3.size)
+        assertEquals("World", map3[1KeySuffix])
+        assertEquals("Monde", map3[2KeySuffix])
+        assertEquals("Welt", map3[3KeySuffix])
+
+        val map4 = pKeyObjectMapOf(
+            1KeySuffix, "World",
+            2KeySuffix, "Monde",
+            3KeySuffix, "Welt",
+            4KeySuffix, "Sekai",
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals("World", map4[1KeySuffix])
+        assertEquals("Monde", map4[2KeySuffix])
+        assertEquals("Welt", map4[3KeySuffix])
+        assertEquals("Sekai", map4[4KeySuffix])
+
+        val map5 = pKeyObjectMapOf(
+            1KeySuffix, "World",
+            2KeySuffix, "Monde",
+            3KeySuffix, "Welt",
+            4KeySuffix, "Sekai",
+            5KeySuffix, "Mondo",
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals("World", map5[1KeySuffix])
+        assertEquals("Monde", map5[2KeySuffix])
+        assertEquals("Welt", map5[3KeySuffix])
+        assertEquals("Sekai", map5[4KeySuffix])
+        assertEquals("Mondo", map5[5KeySuffix])
+    }
+
+    @Test
+    fun mutablePKeyObjectMapInitFunction() {
+        val map1 = mutablePKeyObjectMapOf(
+            1KeySuffix, "World",
+        )
+        assertEquals(1, map1.size)
+        assertEquals("World", map1[1KeySuffix])
+
+        val map2 = mutablePKeyObjectMapOf(
+            1KeySuffix, "World",
+            2KeySuffix, "Monde",
+        )
+        assertEquals(2, map2.size)
+        assertEquals("World", map2[1KeySuffix])
+        assertEquals("Monde", map2[2KeySuffix])
+
+        val map3 = mutablePKeyObjectMapOf(
+            1KeySuffix, "World",
+            2KeySuffix, "Monde",
+            3KeySuffix, "Welt",
+        )
+        assertEquals(3, map3.size)
+        assertEquals("World", map3[1KeySuffix])
+        assertEquals("Monde", map3[2KeySuffix])
+        assertEquals("Welt", map3[3KeySuffix])
+
+        val map4 = mutablePKeyObjectMapOf(
+            1KeySuffix, "World",
+            2KeySuffix, "Monde",
+            3KeySuffix, "Welt",
+            4KeySuffix, "Sekai",
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals("World", map4[1KeySuffix])
+        assertEquals("Monde", map4[2KeySuffix])
+        assertEquals("Welt", map4[3KeySuffix])
+        assertEquals("Sekai", map4[4KeySuffix])
+
+        val map5 = mutablePKeyObjectMapOf(
+            1KeySuffix, "World",
+            2KeySuffix, "Monde",
+            3KeySuffix, "Welt",
+            4KeySuffix, "Sekai",
+            5KeySuffix, "Mondo",
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals("World", map5[1KeySuffix])
+        assertEquals("Monde", map5[2KeySuffix])
+        assertEquals("Welt", map5[3KeySuffix])
+        assertEquals("Sekai", map5[4KeySuffix])
+        assertEquals("Mondo", map5[5KeySuffix])
     }
 
     @Test
@@ -153,25 +252,12 @@
     }
 
     @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"))
+        map.putAll(mutablePKeyObjectMapOf(3KeySuffix, "Welt", 7KeySuffix, "Mundo"))
 
         assertEquals(4, map.size)
         assertEquals("Welt", map[3KeySuffix])
@@ -179,28 +265,9 @@
     }
 
     @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")
+        map += pKeyObjectMapOf(3KeySuffix, "Welt", 7KeySuffix, "Mundo")
 
         assertEquals(2, map.size)
         assertEquals("Welt", map[3KeySuffix])
@@ -520,6 +587,41 @@
     }
 
     @Test
+    fun joinToString() {
+        val map = MutablePKeyObjectMap<String>()
+        repeat(5) {
+            map[it.toPKey()] = it.toString()
+        }
+        val order = IntArray(5)
+        var index = 0
+        map.forEach { key, _ ->
+            order[index++] = key.toInt()
+        }
+        assertEquals(
+            "${order[0].toPKey()}=${order[0]}, ${order[1].toPKey()}=${order[1]}, " +
+            "${order[2].toPKey()}=${order[2]}, ${order[3].toPKey()}=${order[3]}, " +
+            "${order[4].toPKey()}=${order[4]}",
+            map.joinToString()
+        )
+        assertEquals(
+            "x${order[0].toPKey()}=${order[0]}, ${order[1].toPKey()}=${order[1]}, " +
+            "${order[2].toPKey()}=${order[2]}...",
+            map.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${order[0].toPKey()}=${order[0]}-${order[1].toPKey()}=${order[1]}-" +
+            "${order[2].toPKey()}=${order[2]}-${order[3].toPKey()}=${order[3]}-" +
+            "${order[4].toPKey()}=${order[4]}<",
+            map.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        val names = arrayOf("one", "two", "three", "four", "five")
+        assertEquals(
+            "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+            map.joinToString(limit = 3) { key, _ -> names[key.toInt()] }
+        )
+    }
+
+    @Test
     fun equals() {
         val map = MutablePKeyObjectMap<String?>()
         map[1KeySuffix] = "World"
@@ -626,7 +728,7 @@
         map[5KeySuffix] = "Mondo"
         map[6KeySuffix] = "Sesang"
 
-        assertTrue(map.all { key, value -> key < 7KeySuffix && value.length > 0 })
+        assertTrue(map.all { key, value -> key < 7KeySuffix && value.isNotEmpty() })
         assertFalse(map.all { key, _ -> key < 6KeySuffix })
     }
 }
diff --git a/collection/collection/template/PKeyPValueMap.kt.template b/collection/collection/template/PKeyPValueMap.kt.template
index 0d0a6fe..48eaa25 100644
--- a/collection/collection/template/PKeyPValueMap.kt.template
+++ b/collection/collection/template/PKeyPValueMap.kt.template
@@ -22,6 +22,7 @@
 package androidx.collection
 
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -41,7 +42,6 @@
 /**
  * Returns an empty, read-only [PKeyPValueMap].
  */
-@Suppress("UNCHECKED_CAST")
 public fun emptyPKeyPValueMap(): PKeyPValueMap = EmptyPKeyPValueMap
 
 /**
@@ -50,18 +50,87 @@
 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.
+ * Returns a new [PKeyPValueMap] with [key1] associated with [value1].
  */
-public fun pKeyPValueMapOf(vararg pairs: Pair<PKey, PValue>): PKeyPValueMap =
-    MutablePKeyPValueMap(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun pKeyPValueMapOf(
+    key1: PKey,
+    value1: PValue
+): PKeyPValueMap = MutablePKeyPValueMap().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [PKeyPValueMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun pKeyPValueMapOf(
+    key1: PKey,
+    value1: PValue,
+    key2: PKey,
+    value2: PValue,
+): PKeyPValueMap = MutablePKeyPValueMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [PKeyPValueMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun pKeyPValueMapOf(
+    key1: PKey,
+    value1: PValue,
+    key2: PKey,
+    value2: PValue,
+    key3: PKey,
+    value3: PValue,
+): PKeyPValueMap = MutablePKeyPValueMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [PKeyPValueMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun pKeyPValueMapOf(
+    key1: PKey,
+    value1: PValue,
+    key2: PKey,
+    value2: PValue,
+    key3: PKey,
+    value3: PValue,
+    key4: PKey,
+    value4: PValue,
+): PKeyPValueMap = MutablePKeyPValueMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [PKeyPValueMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun pKeyPValueMapOf(
+    key1: PKey,
+    value1: PValue,
+    key2: PKey,
+    value2: PValue,
+    key3: PKey,
+    value3: PValue,
+    key4: PKey,
+    value4: PValue,
+    key5: PKey,
+    value5: PValue,
+): PKeyPValueMap = MutablePKeyPValueMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -70,18 +139,87 @@
 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.
+ * Returns a new [MutablePKeyPValueMap] with [key1] associated with [value1].
  */
-public fun mutablePKeyPValueMapOf(vararg pairs: Pair<PKey, PValue>): MutablePKeyPValueMap =
-    MutablePKeyPValueMap(pairs.size).also { map ->
-        pairs.forEach { (key, value) -> map[key] = value }
+public fun mutablePKeyPValueMapOf(
+    key1: PKey,
+    value1: PValue
+): MutablePKeyPValueMap = MutablePKeyPValueMap().also { map ->
+        map[key1] = value1
+    }
+
+/**
+ * Returns a new [MutablePKeyPValueMap] with [key1], and [key2]
+ * associated with [value1], and [value2], respectively.
+ */
+public fun mutablePKeyPValueMapOf(
+    key1: PKey,
+    value1: PValue,
+    key2: PKey,
+    value2: PValue,
+): MutablePKeyPValueMap = MutablePKeyPValueMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+    }
+
+/**
+ * Returns a new [MutablePKeyPValueMap] with [key1], [key2], and [key3]
+ * associated with [value1], [value2], and [value3], respectively.
+ */
+public fun mutablePKeyPValueMapOf(
+    key1: PKey,
+    value1: PValue,
+    key2: PKey,
+    value2: PValue,
+    key3: PKey,
+    value3: PValue,
+): MutablePKeyPValueMap = MutablePKeyPValueMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+    }
+
+/**
+ * Returns a new [MutablePKeyPValueMap] with [key1], [key2], [key3], and [key4]
+ * associated with [value1], [value2], [value3], and [value4], respectively.
+ */
+public fun mutablePKeyPValueMapOf(
+    key1: PKey,
+    value1: PValue,
+    key2: PKey,
+    value2: PValue,
+    key3: PKey,
+    value3: PValue,
+    key4: PKey,
+    value4: PValue,
+): MutablePKeyPValueMap = MutablePKeyPValueMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+    }
+
+/**
+ * Returns a new [MutablePKeyPValueMap] with [key1], [key2], [key3], [key4], and [key5]
+ * associated with [value1], [value2], [value3], [value4], and [value5], respectively.
+ */
+public fun mutablePKeyPValueMapOf(
+    key1: PKey,
+    value1: PValue,
+    key2: PKey,
+    value2: PValue,
+    key3: PKey,
+    value3: PValue,
+    key4: PKey,
+    value4: PValue,
+    key5: PKey,
+    value5: PValue,
+): MutablePKeyPValueMap = MutablePKeyPValueMap().also { map ->
+        map[key1] = value1
+        map[key2] = value2
+        map[key3] = value3
+        map[key4] = value4
+        map[key5] = value5
     }
 
 /**
@@ -336,6 +474,73 @@
     }
 
     /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@PKeyPValueMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(key)
+            append('=')
+            append(value)
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the entries, separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. Each entry is created with [transform].
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (key: PKey, value: PValue) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@PKeyPValueMap.forEach { key, value ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(key, value))
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
      * Returns the hash code value for this map. The hash code the sum of the hash
      * codes of each key/value pair.
      */
@@ -554,20 +759,6 @@
     }
 
     /**
-     * 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) {
@@ -577,29 +768,6 @@
     }
 
     /**
-     * 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)
diff --git a/collection/collection/template/PKeyPValueMapTest.kt.template b/collection/collection/template/PKeyPValueMapTest.kt.template
index c8f3cd5..e6609c5 100644
--- a/collection/collection/template/PKeyPValueMapTest.kt.template
+++ b/collection/collection/template/PKeyPValueMapTest.kt.template
@@ -78,14 +78,113 @@
     }
 
     @Test
-    fun pKeyPValueMapPairsFunction() {
-        val map = mutablePKeyPValueMapOf(
-            1KeySuffix to 1ValueSuffix,
-            2KeySuffix to 2ValueSuffix
+    fun pKeyPValueMapInitFunction() {
+        val map1 = pKeyPValueMapOf(
+            1KeySuffix, 1ValueSuffix,
         )
-        assertEquals(2, map.size)
-        assertEquals(1ValueSuffix, map[1KeySuffix])
-        assertEquals(2ValueSuffix, map[2KeySuffix])
+        assertEquals(1, map1.size)
+        assertEquals(1ValueSuffix, map1[1KeySuffix])
+
+        val map2 = pKeyPValueMapOf(
+            1KeySuffix, 1ValueSuffix,
+            2KeySuffix, 2ValueSuffix,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1ValueSuffix, map2[1KeySuffix])
+        assertEquals(2ValueSuffix, map2[2KeySuffix])
+
+        val map3 = pKeyPValueMapOf(
+            1KeySuffix, 1ValueSuffix,
+            2KeySuffix, 2ValueSuffix,
+            3KeySuffix, 3ValueSuffix,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1ValueSuffix, map3[1KeySuffix])
+        assertEquals(2ValueSuffix, map3[2KeySuffix])
+        assertEquals(3ValueSuffix, map3[3KeySuffix])
+
+        val map4 = pKeyPValueMapOf(
+            1KeySuffix, 1ValueSuffix,
+            2KeySuffix, 2ValueSuffix,
+            3KeySuffix, 3ValueSuffix,
+            4KeySuffix, 4ValueSuffix,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1ValueSuffix, map4[1KeySuffix])
+        assertEquals(2ValueSuffix, map4[2KeySuffix])
+        assertEquals(3ValueSuffix, map4[3KeySuffix])
+        assertEquals(4ValueSuffix, map4[4KeySuffix])
+
+        val map5 = pKeyPValueMapOf(
+            1KeySuffix, 1ValueSuffix,
+            2KeySuffix, 2ValueSuffix,
+            3KeySuffix, 3ValueSuffix,
+            4KeySuffix, 4ValueSuffix,
+            5KeySuffix, 5ValueSuffix,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1ValueSuffix, map5[1KeySuffix])
+        assertEquals(2ValueSuffix, map5[2KeySuffix])
+        assertEquals(3ValueSuffix, map5[3KeySuffix])
+        assertEquals(4ValueSuffix, map5[4KeySuffix])
+        assertEquals(5ValueSuffix, map5[5KeySuffix])
+    }
+
+    @Test
+    fun mutablePKeyPValueMapInitFunction() {
+        val map1 = mutablePKeyPValueMapOf(
+            1KeySuffix, 1ValueSuffix,
+        )
+        assertEquals(1, map1.size)
+        assertEquals(1ValueSuffix, map1[1KeySuffix])
+
+        val map2 = mutablePKeyPValueMapOf(
+            1KeySuffix, 1ValueSuffix,
+            2KeySuffix, 2ValueSuffix,
+        )
+        assertEquals(2, map2.size)
+        assertEquals(1ValueSuffix, map2[1KeySuffix])
+        assertEquals(2ValueSuffix, map2[2KeySuffix])
+
+        val map3 = mutablePKeyPValueMapOf(
+            1KeySuffix, 1ValueSuffix,
+            2KeySuffix, 2ValueSuffix,
+            3KeySuffix, 3ValueSuffix,
+        )
+        assertEquals(3, map3.size)
+        assertEquals(1ValueSuffix, map3[1KeySuffix])
+        assertEquals(2ValueSuffix, map3[2KeySuffix])
+        assertEquals(3ValueSuffix, map3[3KeySuffix])
+
+        val map4 = mutablePKeyPValueMapOf(
+            1KeySuffix, 1ValueSuffix,
+            2KeySuffix, 2ValueSuffix,
+            3KeySuffix, 3ValueSuffix,
+            4KeySuffix, 4ValueSuffix,
+        )
+
+        assertEquals(4, map4.size)
+        assertEquals(1ValueSuffix, map4[1KeySuffix])
+        assertEquals(2ValueSuffix, map4[2KeySuffix])
+        assertEquals(3ValueSuffix, map4[3KeySuffix])
+        assertEquals(4ValueSuffix, map4[4KeySuffix])
+
+        val map5 = mutablePKeyPValueMapOf(
+            1KeySuffix, 1ValueSuffix,
+            2KeySuffix, 2ValueSuffix,
+            3KeySuffix, 3ValueSuffix,
+            4KeySuffix, 4ValueSuffix,
+            5KeySuffix, 5ValueSuffix,
+        )
+
+        assertEquals(5, map5.size)
+        assertEquals(1ValueSuffix, map5[1KeySuffix])
+        assertEquals(2ValueSuffix, map5[2KeySuffix])
+        assertEquals(3ValueSuffix, map5[3KeySuffix])
+        assertEquals(4ValueSuffix, map5[4KeySuffix])
+        assertEquals(5ValueSuffix, map5[5KeySuffix])
     }
 
     @Test
@@ -146,38 +245,6 @@
     }
 
     @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
@@ -468,6 +535,43 @@
     }
 
     @Test
+    fun joinToString() {
+        val map = MutablePKeyPValueMap()
+        repeat(5) {
+            map[it.toPKey()] = it.toPValue()
+        }
+        val order = IntArray(5)
+        var index = 0
+        map.forEach { key, _ ->
+            order[index++] = key.toInt()
+        }
+        assertEquals(
+            "${order[0].toPKey()}=${order[0].toPValue()}, ${order[1].toPKey()}=" +
+            "${order[1].toPValue()}, ${order[2].toPKey()}=${order[2].toPValue()}," +
+            " ${order[3].toPKey()}=${order[3].toPValue()}, ${order[4].toPKey()}=" +
+            "${order[4].toPValue()}",
+            map.joinToString()
+        )
+        assertEquals(
+            "x${order[0].toPKey()}=${order[0].toPValue()}, ${order[1].toPKey()}=" +
+            "${order[1].toPValue()}, ${order[2].toPKey()}=${order[2].toPValue()}...",
+            map.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${order[0].toPKey()}=${order[0].toPValue()}-${order[1].toPKey()}=" +
+            "${order[1].toPValue()}-${order[2].toPKey()}=${order[2].toPValue()}-" +
+            "${order[3].toPKey()}=${order[3].toPValue()}-${order[4].toPKey()}=" +
+            "${order[4].toPValue()}<",
+            map.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        val names = arrayOf("one", "two", "three", "four", "five")
+        assertEquals(
+            "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+            map.joinToString(limit = 3) { key, _ -> names[key.toInt()] }
+        )
+    }
+
+    @Test
     fun equals() {
         val map = MutablePKeyPValueMap()
         map[1KeySuffix] = 1ValueSuffix
diff --git a/collection/collection/template/PKeySet.kt.template b/collection/collection/template/PKeySet.kt.template
index f950f03..c3fedd9 100644
--- a/collection/collection/template/PKeySet.kt.template
+++ b/collection/collection/template/PKeySet.kt.template
@@ -28,6 +28,7 @@
 
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
+import kotlin.jvm.JvmOverloads
 
 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 // DO NOT MAKE CHANGES to the kotlin source file.
@@ -321,6 +322,71 @@
     public operator fun contains(element: PKey): Boolean = findElementIndex(element) >= 0
 
     /**
+     * Creates a String from the elements separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@PKeySet.forEach { element ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(element)
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
+     * Creates a String from the elements separated by [separator] and using [prefix] before
+     * and [postfix] after, if supplied. [transform] dictates how each element will be represented.
+     *
+     * When a non-negative value of [limit] is provided, a maximum of [limit] items are used
+     * to generate the string. If the collection holds more than [limit] items, the string
+     * is terminated with [truncated].
+     */
+    @JvmOverloads
+    public inline fun joinToString(
+        separator: CharSequence = ", ",
+        prefix: CharSequence = "",
+        postfix: CharSequence = "", // I know this should be suffix, but this is kotlin's name
+        limit: Int = -1,
+        truncated: CharSequence = "...",
+        crossinline transform: (PKey) -> CharSequence
+    ): String = buildString {
+        append(prefix)
+        var index = 0
+        this@PKeySet.forEach { element ->
+            if (index == limit) {
+                append(truncated)
+                return@buildString
+            }
+            if (index != 0) {
+                append(separator)
+            }
+            append(transform(element))
+            index++
+        }
+        append(postfix)
+    }
+
+    /**
      * 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.
      */
@@ -366,23 +432,7 @@
      * 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()
-    }
+    override fun toString(): String = joinToString(prefix = "[", postfix = "]")
 
     /**
      * Scans the set to find the index in the backing arrays of the
diff --git a/collection/collection/template/PKeySetTest.kt.template b/collection/collection/template/PKeySetTest.kt.template
index cd5a422..6962ded 100644
--- a/collection/collection/template/PKeySetTest.kt.template
+++ b/collection/collection/template/PKeySetTest.kt.template
@@ -347,6 +347,35 @@
     }
 
     @Test
+    fun joinToString() {
+        val set = pKeySetOf(1KeySuffix, 2KeySuffix, 3KeySuffix, 4KeySuffix, 5KeySuffix)
+        val order = IntArray(5)
+        var index = 0
+        set.forEach { element ->
+            order[index++] = element.toInt()
+        }
+        assertEquals(
+            "${order[0].toPKey()}, ${order[1].toPKey()}, ${order[2].toPKey()}, " +
+            "${order[3].toPKey()}, ${order[4].toPKey()}",
+            set.joinToString()
+        )
+        assertEquals(
+            "x${order[0].toPKey()}, ${order[1].toPKey()}, ${order[2].toPKey()}...",
+            set.joinToString(prefix = "x", postfix = "y", limit = 3)
+        )
+        assertEquals(
+            ">${order[0].toPKey()}-${order[1].toPKey()}-${order[2].toPKey()}-" +
+            "${order[3].toPKey()}-${order[4].toPKey()}<",
+            set.joinToString(separator = "-", prefix = ">", postfix = "<")
+        )
+        val names = arrayOf("one", "two", "three", "four", "five")
+        assertEquals(
+            "${names[order[0]]}, ${names[order[1]]}, ${names[order[2]]}...",
+            set.joinToString(limit = 3) { names[it.toInt()] }
+        )
+    }
+
+    @Test
     fun equals() {
         val set = MutablePKeySet()
         set += 1KeySuffix
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 361d875..d0804c6 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -1198,6 +1198,8 @@
     method public boolean isScrollInProgress();
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public final suspend Object? scrollToPage(int page, optional float pageOffsetFraction, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public final void updateCurrentPage(androidx.compose.foundation.gestures.ScrollScope, int page, optional float pageOffsetFraction);
+    method public final void updateTargetPage(androidx.compose.foundation.gestures.ScrollScope, int targetPage);
     property public final boolean canScrollBackward;
     property public final boolean canScrollForward;
     property public final int currentPage;
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 5a3ef8a..d5052df 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -1200,6 +1200,8 @@
     method public boolean isScrollInProgress();
     method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public final suspend Object? scrollToPage(int page, optional float pageOffsetFraction, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public final void updateCurrentPage(androidx.compose.foundation.gestures.ScrollScope, int page, optional float pageOffsetFraction);
+    method public final void updateTargetPage(androidx.compose.foundation.gestures.ScrollScope, int targetPage);
     property public final boolean canScrollBackward;
     property public final boolean canScrollForward;
     property public final int currentPage;
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/lint-baseline.xml b/compose/foundation/foundation/integration-tests/foundation-demos/lint-baseline.xml
index 4944215..652b3f1 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/lint-baseline.xml
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/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.3.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
 
     <issue
         id="NewApi"
@@ -102,69 +102,6 @@
 
     <issue
         id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method SnapLayoutInfoProvider has parameter &apos;itemSize&apos; with type Function1&lt;? super Density, Float>."
-        errorLine1="    itemSize: Density.() -> Float,"
-        errorLine2="              ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/foundation/demos/snapping/RowSnapLayoutInfoProvider.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method SnapLayoutInfoProvider has parameter &apos;layoutSize&apos; with type Function1&lt;? super Density, Float>."
-        errorLine1="    layoutSize: Density.() -> Float"
-        errorLine2="                ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/foundation/demos/snapping/RowSnapLayoutInfoProvider.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in variable &apos;isValidDistance&apos; with type Function1&lt;? super Float, ? extends Boolean>."
-        errorLine1="    fun Float.isValidDistance(): Boolean {"
-        errorLine2="    ^">
-        <location
-            file="src/main/java/androidx/compose/foundation/demos/snapping/RowSnapLayoutInfoProvider.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method RowSnappingMainLayout has parameter &apos;onLayoutSizeChanged&apos; with type Function1&lt;? super IntSize, Unit>."
-        errorLine1="    onLayoutSizeChanged: (IntSize) -> Unit"
-        errorLine2="                         ~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/foundation/demos/snapping/RowSnappingDemos.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method rememberRowSnapLayoutInfoProvider has parameter &apos;layoutSize&apos; with type Function1&lt;? super Density, Float>."
-        errorLine1="    layoutSize: Density.() -> Float"
-        errorLine2="                ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/foundation/demos/snapping/RowSnappingDemos.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method rememberMultiPageRowSnapLayoutInfoProvider has parameter &apos;layoutSize&apos; with type Function1&lt;? super Density, Float>."
-        errorLine1="    layoutSize: Density.() -> Float"
-        errorLine2="                ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/foundation/demos/snapping/RowSnappingDemos.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method rememberViewPortRowSnapLayoutInfoProvider has parameter &apos;layoutSize&apos; with type Function1&lt;? super Density, Float>."
-        errorLine1="    layoutSize: Density.() -> Float"
-        errorLine2="                ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/foundation/demos/snapping/RowSnappingDemos.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
         message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method ResizeHandle has parameter &apos;onDrag&apos; with type Function1&lt;? super Float, Unit>."
         errorLine1="private fun ResizeHandle(orientation: Orientation, onDrag: (Float) -> Unit) {"
         errorLine2="                                                           ~~~~~~~~~~~~~~~">
@@ -183,24 +120,6 @@
 
     <issue
         id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in constructor ViewPortBasedSnappingLayoutInfoProvider has parameter &apos;viewPortStep&apos; with type Function0&lt;Float>."
-        errorLine1="    private val viewPortStep: () -> Float"
-        errorLine2="                              ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/foundation/demos/snapping/SnappingDemos.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method SnappingDemoMainLayout has parameter &apos;content&apos; with type Function1&lt;? super Integer, Unit>."
-        errorLine1="    content: @Composable (Int) -> Unit"
-        errorLine2="             ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/foundation/demos/snapping/SnappingDemos.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
         message="Use a functional interface instead of lambda syntax for lambdas with primitive values in variable &apos;setDemoIndex&apos; with type Function1&lt;? super Integer, ? extends Unit>."
         errorLine1="    val (currentDemoIndex, setDemoIndex) = rememberSaveable { mutableIntStateOf(-1) }"
         errorLine2="                           ~~~~~~~~~~~~">
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
deleted file mode 100644
index 3e596cc..0000000
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeTextSelectionLazy.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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/ComposeTextSelectionScrollable.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeTextSelectionScrollable.kt
new file mode 100644
index 0000000..d46aded
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeTextSelectionScrollable.kt
@@ -0,0 +1,126 @@
+/*
+ * 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 android.annotation.SuppressLint
+import androidx.compose.foundation.layout.Arrangement.spacedBy
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.selection.selectable
+import androidx.compose.foundation.selection.selectableGroup
+import androidx.compose.foundation.text.selection.SelectionContainer
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.RadioButton
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+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
+
+@SuppressLint("PrimitiveInLambda")
+@Preview
+@Composable
+fun TextScrollableColumnSelectionDemo() {
+    val spacing = 16.dp
+    Column(
+        modifier = Modifier.padding(spacing),
+        verticalArrangement = spacedBy(spacing)
+    ) {
+        Text(
+            text = "We expect that selection works, " +
+                "regardless of how many times each text goes in or out of view. " +
+                "The selection handles and text toolbar also should follow the selection " +
+                "when it is scrolled.",
+            style = MaterialTheme.typography.body1.merge(),
+        )
+        val (selectedOption, onOptionSelected) = remember {
+            mutableStateOf(Options.LongScrollableText)
+        }
+        Column(Modifier.selectableGroup()) {
+            Options.values().forEach { option ->
+                Row(
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .selectable(
+                            selected = option == selectedOption,
+                            onClick = { onOptionSelected(option) }
+                        ),
+                    verticalAlignment = Alignment.CenterVertically,
+                ) {
+                    RadioButton(
+                        selected = option == selectedOption,
+                        onClick = { onOptionSelected(option) }
+                    )
+                    Text(
+                        text = option.displayText,
+                        style = MaterialTheme.typography.body1.merge(),
+                    )
+                }
+            }
+        }
+        selectedOption.Content()
+    }
+}
+
+@Suppress("unused") // enum values used in .values()
+private enum class Options(val displayText: String, val content: @Composable () -> Unit) {
+    LongScrollableText("Long Scrollable Single Text", {
+        MyText(
+            modifier = Modifier.verticalScroll(rememberScrollState()),
+            text = (0..100).joinToString(separator = "\n") { it.toString() },
+        )
+    }),
+    LongTextScrollableColumn("Long Single Text in Scrollable Column", {
+        Column(Modifier.verticalScroll(rememberScrollState())) {
+            MyText((0..100).joinToString(separator = "\n") { it.toString() })
+        }
+    }),
+    MultiTextScrollableColumn("Multiple Texts in Scrollable Column", {
+        Column(Modifier.verticalScroll(rememberScrollState())) {
+            repeat(100) { MyText(it.toString()) }
+        }
+    }),
+    MultiTextLazyColumn("Multiple Texts in LazyColumn", {
+        LazyColumn {
+            items(100) { MyText(it.toString()) }
+        }
+    });
+
+    @Composable
+    fun Content() {
+        SelectionContainer(content = content)
+    }
+}
+
+@Composable
+private fun MyText(text: String, modifier: Modifier = Modifier) {
+    Text(
+        text = text,
+        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 90304cc..ef5b5ac 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,7 +152,9 @@
                 ComposableDemo("Text selection") { TextSelectionDemo() },
                 ComposableDemo("Text selection sample") { TextSelectionSample() },
                 ComposableDemo("Overflowed Selection") { TextOverflowedSelectionDemo() },
-                ComposableDemo("LazyColumn Text Selection") { TextLazySelectionDemo() },
+                ComposableDemo("Scrollable Column Text Selection") {
+                    TextScrollableColumnSelectionDemo()
+                },
             )
         ),
         DemoCategory(
diff --git a/compose/foundation/foundation/lint-baseline.xml b/compose/foundation/foundation/lint-baseline.xml
index 1f61298..40e6813 100644
--- a/compose/foundation/foundation/lint-baseline.xml
+++ b/compose/foundation/foundation/lint-baseline.xml
@@ -1,14 +1,5 @@
 <?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="NewApi"
-        message="Class requires API level 28 (current min is 21): `PlatformMagnifierImpl`"
-        errorLine1="                    ) as PlatformMagnifierFactoryApi28Impl.PlatformMagnifierImpl"
-        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidInstrumentedTest/kotlin/androidx/compose/foundation/PlatformMagnifierTest.kt"/>
-    </issue>
+<issues format="6" by="lint 8.3.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
 
     <issue
         id="BanSuppressTag"
@@ -56,24 +47,6 @@
     </issue>
 
     <issue
-        id="ExperimentalPropertyAnnotation"
-        message="This property does not have all required annotations to correctly mark it as experimental."
-        errorLine1="        @ExperimentalFoundationApi"
-        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/foundation/Magnifier.kt"/>
-    </issue>
-
-    <issue
-        id="ExperimentalPropertyAnnotation"
-        message="This property does not have all required annotations to correctly mark it as experimental."
-        errorLine1="        @ExperimentalFoundationApi"
-        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/foundation/Magnifier.kt"/>
-    </issue>
-
-    <issue
         id="PrimitiveInLambda"
         message="Use a functional interface instead of lambda syntax for lambdas with primitive values in constructor AnchoredDraggableState has parameter &apos;positionalThreshold&apos; with type Function1&lt;? super Float, Float>."
         errorLine1="    internal val positionalThreshold: (totalDistance: Float) -> Float,"
@@ -156,24 +129,6 @@
 
     <issue
         id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method BasicSecureTextField has parameter &apos;onSubmit&apos; with type Function1&lt;? super ImeAction, Boolean>."
-        errorLine1="    onSubmit: ((ImeAction) -> Boolean)? = null,"
-        errorLine2="              ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/foundation/text2/BasicSecureTextField.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method KeyboardActions has parameter &apos;onSubmit&apos; with type Function1&lt;? super ImeAction, Boolean>."
-        errorLine1="private fun KeyboardActions(onSubmit: (ImeAction) -> Boolean) = KeyboardActions("
-        errorLine2="                                      ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/foundation/text2/BasicSecureTextField.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
         message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method ClickableText has parameter &apos;onClick&apos; with type Function1&lt;? super Integer, Unit>."
         errorLine1="    onClick: (Int) -> Unit"
         errorLine2="             ~~~~~~~~~~~~~">
@@ -1264,7 +1219,7 @@
     <issue
         id="PrimitiveInLambda"
         message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method magnifier has parameter &apos;onSizeChanged&apos; with type Function1&lt;? super DpSize, Unit>."
-        errorLine1="    onSizeChanged: ((DpSize) -> Unit)? = null"
+        errorLine1="    onSizeChanged: ((DpSize) -> Unit)? = null,"
         errorLine2="                   ~~~~~~~~~~~~~~~~~~~">
         <location
             file="src/androidMain/kotlin/androidx/compose/foundation/Magnifier.kt"/>
@@ -1470,42 +1425,6 @@
 
     <issue
         id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method HorizontalPager has parameter &apos;key&apos; with type Function1&lt;? super Integer, ? extends Object>."
-        errorLine1="    key: ((index: Int) -> Any)? = null,"
-        errorLine2="         ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method HorizontalPager has parameter &apos;pageContent&apos; with type Function2&lt;? super PagerScope, ? super Integer, Unit>."
-        errorLine1="    pageContent: @Composable PagerScope.(page: Int) -> Unit"
-        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method VerticalPager has parameter &apos;key&apos; with type Function1&lt;? super Integer, ? extends Object>."
-        errorLine1="    key: ((index: Int) -> Any)? = null,"
-        errorLine2="         ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method VerticalPager has parameter &apos;pageContent&apos; with type Function2&lt;? super PagerScope, ? super Integer, Unit>."
-        errorLine1="    pageContent: @Composable PagerScope.(page: Int) -> Unit"
-        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
         message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method VerticalPager has parameter &apos;key&apos; with type Function1&lt;? super Integer, ? extends Object>."
         errorLine1="    key: ((index: Int) -> Any)? = null,"
         errorLine2="         ~~~~~~~~~~~~~~~~~~~~~~">
@@ -1686,15 +1605,6 @@
 
     <issue
         id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method adjustByBoundary has parameter &apos;boundaryFun&apos; with type Function1&lt;? super Integer, TextRange>."
-        errorLine1="            boundaryFun: (Int) -> TextRange"
-        errorLine2="                         ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionAdjustment.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
         message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method animatedSelectionMagnifier has parameter &apos;magnifierCenter&apos; with type Function0&lt;Offset>."
         errorLine1="    magnifierCenter: () -> Offset,"
         errorLine2="                     ~~~~~~~~~~~~">
@@ -1722,15 +1632,6 @@
 
     <issue
         id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in variable &apos;getMagnifierCenter&apos; with type Function2&lt;? super AnchorInfo, ? super Boolean, ? extends Offset>."
-        errorLine1="    fun getMagnifierCenter(anchor: AnchorInfo, isStartHandle: Boolean): Offset {"
-        errorLine2="    ^">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
         message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method setOnPositionChangeCallback$lint_module has parameter &apos;&lt;set-?>&apos; with type Function1&lt;? super Long, Unit>."
         errorLine1="    /**"
         errorLine2="    ^">
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/PagerSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/PagerSamples.kt
index 979c731..b845444 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/PagerSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/PagerSamples.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation.samples
 
 import androidx.annotation.Sampled
+import androidx.compose.animation.core.animate
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
@@ -30,6 +31,7 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.pager.HorizontalPager
 import androidx.compose.foundation.pager.PageSize
+import androidx.compose.foundation.pager.PagerState
 import androidx.compose.foundation.pager.VerticalPager
 import androidx.compose.foundation.pager.rememberPagerState
 import androidx.compose.foundation.rememberScrollState
@@ -130,6 +132,66 @@
 @OptIn(ExperimentalFoundationApi::class)
 @Sampled
 @Composable
+fun PagerCustomAnimateScrollToPage() {
+    suspend fun PagerState.customAnimateScrollToPage(page: Int) {
+        val preJumpPosition = if (page > currentPage) {
+            (page - 1).coerceAtLeast(0)
+        } else {
+            (page + 1).coerceAtMost(pageCount)
+        }
+        scroll {
+            // Update the target page
+            updateTargetPage(page)
+
+            // pre-jump to 1 page before our target page
+            updateCurrentPage(preJumpPosition, 0.0f)
+            val targetPageDiff = page - currentPage
+            val distance = targetPageDiff * layoutInfo.pageSize.toFloat()
+            var previousValue = 0.0f
+            animate(
+                0f,
+                distance,
+            ) { currentValue, _ ->
+                previousValue += scrollBy(currentValue - previousValue)
+            }
+        }
+    }
+
+    val state = rememberPagerState(initialPage = 5) { 10 }
+    val scope = rememberCoroutineScope()
+
+    Column {
+        HorizontalPager(
+            modifier = Modifier
+                .fillMaxSize()
+                .weight(0.9f),
+            state = state
+        ) { page ->
+            Box(
+                modifier = Modifier
+                    .padding(10.dp)
+                    .background(Color.Blue)
+                    .fillMaxWidth()
+                    .aspectRatio(1f),
+                contentAlignment = Alignment.Center
+            ) {
+                Text(text = page.toString(), fontSize = 32.sp)
+            }
+        }
+
+        Button(onClick = {
+            scope.launch {
+                state.customAnimateScrollToPage(1)
+            }
+        }) {
+            Text(text = "Jump to Page 1")
+        }
+    }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+@Sampled
+@Composable
 fun CustomPageSizeSample() {
 
     // [PageSize] should be defined as a top level constant in order to avoid unnecessary re-
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridBeyondBoundsTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridBeyondBoundsTest.kt
index 107ee85..7f12de1 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridBeyondBoundsTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridBeyondBoundsTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.list.PlacementComparator
 import androidx.compose.foundation.lazy.list.TrackPlacedElement
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
@@ -25,6 +26,7 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.layout.BeyondBoundsLayout
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Above
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.After
@@ -71,9 +73,11 @@
     private val beyondBoundsLayoutDirection = param.beyondBoundsLayoutDirection
     private val reverseLayout = param.reverseLayout
     private val layoutDirection = param.layoutDirection
-    private val placedItems = mutableSetOf<Int>()
+    private val placedItems = sortedMapOf<Int, Rect>()
     private var beyondBoundsLayout: BeyondBoundsLayout? = null
     private lateinit var lazyGridState: LazyGridState
+    private val placementComparator =
+        PlacementComparator(beyondBoundsLayoutDirection, layoutDirection, reverseLayout)
 
     companion object {
         @JvmStatic
@@ -104,7 +108,7 @@
 
         // Assert.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(0)
+            assertThat(placedItems.keys).containsExactly(0)
             assertThat(visibleItems).containsExactly(0)
         }
     }
@@ -124,7 +128,7 @@
 
         // Assert.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(0, 1)
+            assertThat(placedItems.keys).containsExactly(0, 1)
             assertThat(visibleItems).containsExactly(0, 1)
         }
     }
@@ -144,7 +148,7 @@
 
         // Assert.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(0, 1, 2)
+            assertThat(placedItems.keys).containsExactly(0, 1, 2)
             assertThat(visibleItems).containsExactly(0, 1, 2)
         }
     }
@@ -217,12 +221,14 @@
             beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
                 // Assert that the beyond bounds items are present.
                 if (expectedExtraItemsBeforeVisibleBounds()) {
-                    assertThat(placedItems).containsExactly(4, 5, 6, 7)
-                    assertThat(visibleItems).containsExactly(5, 6, 7)
+                    assertThat(placedItems.keys).containsExactly(4, 5, 6, 7)
                 } else {
-                    assertThat(placedItems).containsExactly(5, 6, 7, 8)
-                    assertThat(visibleItems).containsExactly(5, 6, 7)
+                    assertThat(placedItems.keys).containsExactly(5, 6, 7, 8)
                 }
+                assertThat(visibleItems).containsExactly(5, 6, 7)
+
+                assertThat(placedItems.values).isInOrder(placementComparator)
+
                 // Just return true so that we stop as soon as we run this once.
                 // This should result in one extra item being added.
                 true
@@ -231,7 +237,7 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(placedItems.keys).containsExactly(5, 6, 7)
             assertThat(visibleItems).containsExactly(5, 6, 7)
         }
     }
@@ -275,12 +281,14 @@
             beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
                 // Assert that the beyond bounds items are present.
                 if (expectedExtraItemsBeforeVisibleBounds()) {
-                    assertThat(placedItems).containsExactly(9, 10, 11, 12, 13, 14, 15)
-                    assertThat(visibleItems).containsExactly(10, 11, 12, 13, 14, 15)
+                    assertThat(placedItems.keys).containsExactly(9, 10, 11, 12, 13, 14, 15)
                 } else {
-                    assertThat(placedItems).containsExactly(10, 11, 12, 13, 14, 15, 16)
-                    assertThat(visibleItems).containsExactly(10, 11, 12, 13, 14, 15)
+                    assertThat(placedItems.keys).containsExactly(10, 11, 12, 13, 14, 15, 16)
                 }
+                assertThat(visibleItems).containsExactly(10, 11, 12, 13, 14, 15)
+
+                assertThat(placedItems.values).isInOrder(placementComparator)
+
                 // Just return true so that we stop as soon as we run this once.
                 // This should result in one extra item being added.
                 true
@@ -289,7 +297,7 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(10, 11, 12, 13, 14, 15)
+            assertThat(placedItems.keys).containsExactly(10, 11, 12, 13, 14, 15)
             assertThat(visibleItems).containsExactly(10, 11, 12, 13, 14, 15)
         }
     }
@@ -334,12 +342,14 @@
                 } else {
                     // Assert that the beyond bounds items are present.
                     if (expectedExtraItemsBeforeVisibleBounds()) {
-                        assertThat(placedItems).containsExactly(3, 4, 5, 6, 7)
-                        assertThat(visibleItems).containsExactly(5, 6, 7)
+                        assertThat(placedItems.keys).containsExactly(3, 4, 5, 6, 7)
                     } else {
-                        assertThat(placedItems).containsExactly(5, 6, 7, 8, 9)
-                        assertThat(visibleItems).containsExactly(5, 6, 7)
+                        assertThat(placedItems.keys).containsExactly(5, 6, 7, 8, 9)
                     }
+                    assertThat(visibleItems).containsExactly(5, 6, 7)
+
+                    assertThat(placedItems.values).isInOrder(placementComparator)
+
                     // Return true to stop the search.
                     true
                 }
@@ -348,7 +358,7 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(placedItems.keys).containsExactly(5, 6, 7)
             assertThat(visibleItems).containsExactly(5, 6, 7)
         }
     }
@@ -392,12 +402,14 @@
                 } else {
                     // Assert that the beyond bounds items are present.
                     if (expectedExtraItemsBeforeVisibleBounds()) {
-                        assertThat(placedItems).containsExactly(0, 1, 2, 3, 4, 5, 6, 7)
-                        assertThat(visibleItems).containsExactly(5, 6, 7)
+                        assertThat(placedItems.keys).containsExactly(0, 1, 2, 3, 4, 5, 6, 7)
                     } else {
-                        assertThat(placedItems).containsExactly(5, 6, 7, 8, 9, 10)
-                        assertThat(visibleItems).containsExactly(5, 6, 7)
+                        assertThat(placedItems.keys).containsExactly(5, 6, 7, 8, 9, 10)
                     }
+                    assertThat(visibleItems).containsExactly(5, 6, 7)
+
+                    assertThat(placedItems.values).isInOrder(placementComparator)
+
                     // Return true to end the search.
                     true
                 }
@@ -406,7 +418,7 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(placedItems.keys).containsExactly(5, 6, 7)
         }
     }
 
@@ -441,7 +453,7 @@
             }
         }
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(placedItems.keys).containsExactly(5, 6, 7)
             assertThat(visibleItems).containsExactly(5, 6, 7)
         }
 
@@ -451,15 +463,15 @@
                 beyondBoundsLayoutCount++
                 when (beyondBoundsLayoutDirection) {
                     Left, Right, Above, Below -> {
-                        assertThat(placedItems).containsExactly(5, 6, 7)
+                        assertThat(placedItems.keys).containsExactly(5, 6, 7)
                         assertThat(visibleItems).containsExactly(5, 6, 7)
                     }
                     Before, After -> {
                         if (expectedExtraItemsBeforeVisibleBounds()) {
-                            assertThat(placedItems).containsExactly(4, 5, 6, 7)
+                            assertThat(placedItems.keys).containsExactly(4, 5, 6, 7)
                             assertThat(visibleItems).containsExactly(5, 6, 7)
                         } else {
-                            assertThat(placedItems).containsExactly(5, 6, 7, 8)
+                            assertThat(placedItems.keys).containsExactly(5, 6, 7, 8)
                             assertThat(visibleItems).containsExactly(5, 6, 7)
                         }
                     }
@@ -479,7 +491,7 @@
                     assertThat(beyondBoundsLayoutCount).isEqualTo(1)
 
                     // Assert that the beyond bounds items are removed.
-                    assertThat(placedItems).containsExactly(5, 6, 7)
+                    assertThat(placedItems.keys).containsExactly(5, 6, 7)
                     assertThat(visibleItems).containsExactly(5, 6, 7)
                 }
                 else -> error("Unsupported BeyondBoundsLayoutDirection")
@@ -530,7 +542,7 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(placedItems.keys).containsExactly(5, 6, 7)
             assertThat(visibleItems).containsExactly(5, 6, 7)
         }
     }
@@ -618,5 +630,5 @@
     )
 
     private fun Modifier.trackPlaced(index: Int): Modifier =
-        this then TrackPlacedElement(placedItems, index)
+        this then TrackPlacedElement(index, placedItems)
 }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsAndExtraItemsTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsAndExtraItemsTest.kt
index 9c541a1..01d26d2 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsAndExtraItemsTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsAndExtraItemsTest.kt
@@ -20,9 +20,11 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.layout.BeyondBoundsLayout
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Above
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.After
@@ -37,7 +39,6 @@
 import androidx.compose.ui.unit.dp
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.runBlocking
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -50,16 +51,20 @@
     private val beyondBoundsLayoutDirection = config.beyondBoundsLayoutDirection
     private val reverseLayout = config.reverseLayout
     private val layoutDirection = config.layoutDirection
-    private val placedItems = mutableSetOf<Int>()
+    private val placedItems = sortedMapOf<Int, Rect>()
+    private val placementComparator =
+        PlacementComparator(beyondBoundsLayoutDirection, layoutDirection, reverseLayout)
 
     @OptIn(ExperimentalComposeUiApi::class)
     @Test
     fun verifyItemsArePlacedBeforeBeyondBoundsItems_oneBeyondBoundItem() {
         // Arrange
         var beyondBoundsLayout: BeyondBoundsLayout? = null
-        val lazyListState = LazyListState()
+        val firstVisibleItemIndex = 5
+        var lazyListState = LazyListState()
         rule.setContent {
             CompositionLocalProvider(LocalLayoutDirection provides layoutDirection) {
+                lazyListState = rememberLazyListState(firstVisibleItemIndex)
                 LazyColumnOrRow(
                     modifier = Modifier.size(30.dp),
                     state = lazyListState,
@@ -93,25 +98,27 @@
                 }
             }
         }
-        rule.runOnIdle { runBlocking { lazyListState.scrollToItem(5) } }
 
         // Act
         rule.runOnUiThread {
             beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
                 // Beyond bounds items are present.
                 if (expectedExtraItemsBeforeVisibleBounds()) {
-                    assertThat(placedItems).containsAtLeast(3, 4, 5, 6, 7, 8)
+                    assertThat(placedItems.keys).containsAtLeast(3, 4, 5, 6, 7, 8)
                 } else {
-                    assertThat(placedItems).containsAtLeast(4, 5, 6, 7, 8, 9)
+                    assertThat(placedItems.keys).containsAtLeast(4, 5, 6, 7, 8, 9)
                 }
                 assertThat(lazyListState.visibleItems).containsAtLeast(5, 6, 7)
+
+                assertThat(placedItems.values).isInOrder(placementComparator)
+
                 true
             }
         }
 
         // Beyond bounds items are removed.
         rule.runOnIdle {
-            assertThat(placedItems).containsAtLeast(4, 5, 6, 7, 8)
+            assertThat(placedItems.keys).containsAtLeast(4, 5, 6, 7, 8)
             assertThat(lazyListState.visibleItems).containsAtLeast(5, 6, 7)
         }
     }
@@ -121,10 +128,12 @@
     fun verifyItemsArePlacedBeforeBeyondBoundsItems_twoBeyondBoundItem() {
         // Arrange
         var beyondBoundsLayout: BeyondBoundsLayout? = null
-        val lazyListState = LazyListState()
+        val firstVisibleItemIndex = 5
+        var lazyListState = LazyListState()
         var extraItemCount = 2
         rule.setContent {
             CompositionLocalProvider(LocalLayoutDirection provides layoutDirection) {
+                lazyListState = rememberLazyListState(firstVisibleItemIndex)
                 LazyColumnOrRow(
                     modifier = Modifier.size(30.dp),
                     state = lazyListState,
@@ -158,7 +167,6 @@
                 }
             }
         }
-        rule.runOnIdle { runBlocking { lazyListState.scrollToItem(5) } }
 
         // Act
         rule.runOnUiThread {
@@ -169,11 +177,14 @@
                 } else {
                     // Beyond bounds items are present.
                     if (expectedExtraItemsBeforeVisibleBounds()) {
-                        assertThat(placedItems).containsAtLeast(2, 3, 4, 5, 6, 7, 8)
+                        assertThat(placedItems.keys).containsAtLeast(2, 3, 4, 5, 6, 7, 8)
                     } else {
-                        assertThat(placedItems).containsAtLeast(4, 5, 6, 7, 8, 9, 10)
+                        assertThat(placedItems.keys).containsAtLeast(4, 5, 6, 7, 8, 9, 10)
                     }
                     assertThat(lazyListState.visibleItems).containsAtLeast(5, 6, 7)
+
+                    assertThat(placedItems.values).isInOrder(placementComparator)
+
                     true
                 }
             }
@@ -181,7 +192,7 @@
 
         // Beyond bounds items are removed
         rule.runOnIdle {
-            assertThat(placedItems).containsAtLeast(4, 5, 6, 7, 8)
+            assertThat(placedItems.keys).containsAtLeast(4, 5, 6, 7, 8)
             assertThat(lazyListState.visibleItems).containsAtLeast(5, 6, 7)
         }
     }
@@ -244,5 +255,5 @@
     }
 
     private fun Modifier.trackPlaced(index: Int): Modifier =
-        this then TrackPlacedElement(placedItems, index)
+        this then TrackPlacedElement(index, placedItems)
 }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsTest.kt
index 4e0a1ed..afd0020 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsTest.kt
@@ -29,6 +29,7 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.layout.BeyondBoundsLayout
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Above
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.After
@@ -38,6 +39,7 @@
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Right
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.ModifierLocalBeyondBoundsLayout
+import androidx.compose.ui.layout.findRootCoordinates
 import androidx.compose.ui.modifier.modifierLocalConsumer
 import androidx.compose.ui.node.LayoutAwareModifierNode
 import androidx.compose.ui.node.ModifierNodeElement
@@ -79,9 +81,11 @@
     private val beyondBoundsLayoutDirection = param.beyondBoundsLayoutDirection
     private val reverseLayout = param.reverseLayout
     private val layoutDirection = param.layoutDirection
-    private val placedItems = mutableSetOf<Int>()
+    private val placedItems = sortedMapOf<Int, Rect>()
     private var beyondBoundsLayout: BeyondBoundsLayout? = null
     private lateinit var lazyListState: LazyListState
+    private val placementComparator =
+        PlacementComparator(beyondBoundsLayoutDirection, layoutDirection, reverseLayout)
 
     companion object {
         @JvmStatic
@@ -112,7 +116,7 @@
 
         // Assert.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(0)
+            assertThat(placedItems.keys).containsExactly(0)
             assertThat(visibleItems).containsExactly(0)
         }
     }
@@ -132,7 +136,7 @@
 
         // Assert.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(0, 1)
+            assertThat(placedItems.keys).containsExactly(0, 1)
             assertThat(visibleItems).containsExactly(0, 1)
         }
     }
@@ -152,7 +156,7 @@
 
         // Assert.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(0, 1, 2)
+            assertThat(placedItems.keys).containsExactly(0, 1, 2)
             assertThat(visibleItems).containsExactly(0, 1, 2)
         }
     }
@@ -203,12 +207,13 @@
                 )
             }
             item {
-                Box(Modifier
-                    .size(10.toDp())
-                    .trackPlaced(5)
-                    .modifierLocalConsumer {
-                        beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
-                    }
+                Box(
+                    Modifier
+                        .size(10.toDp())
+                        .trackPlaced(5)
+                        .modifierLocalConsumer {
+                            beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
+                        }
                 )
             }
             items(5) { index ->
@@ -225,12 +230,14 @@
             beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
                 // Assert that the beyond bounds items are present.
                 if (expectedExtraItemsBeforeVisibleBounds()) {
-                    assertThat(placedItems).containsExactly(4, 5, 6, 7)
-                    assertThat(visibleItems).containsExactly(5, 6, 7)
+                    assertThat(placedItems.keys).containsExactly(4, 5, 6, 7)
                 } else {
-                    assertThat(placedItems).containsExactly(5, 6, 7, 8)
-                    assertThat(visibleItems).containsExactly(5, 6, 7)
+                    assertThat(placedItems.keys).containsExactly(5, 6, 7, 8)
                 }
+                assertThat(visibleItems).containsExactly(5, 6, 7)
+
+                assertThat(placedItems.values).isInOrder(placementComparator)
+
                 // Just return true so that we stop as soon as we run this once.
                 // This should result in one extra item being added.
                 true
@@ -239,7 +246,7 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(placedItems.keys).containsExactly(5, 6, 7)
             assertThat(visibleItems).containsExactly(5, 6, 7)
         }
     }
@@ -284,12 +291,14 @@
                 } else {
                     // Assert that the beyond bounds items are present.
                     if (expectedExtraItemsBeforeVisibleBounds()) {
-                        assertThat(placedItems).containsExactly(3, 4, 5, 6, 7)
-                        assertThat(visibleItems).containsExactly(5, 6, 7)
+                        assertThat(placedItems.keys).containsExactly(3, 4, 5, 6, 7)
                     } else {
-                        assertThat(placedItems).containsExactly(5, 6, 7, 8, 9)
-                        assertThat(visibleItems).containsExactly(5, 6, 7)
+                        assertThat(placedItems.keys).containsExactly(5, 6, 7, 8, 9)
                     }
+                    assertThat(visibleItems).containsExactly(5, 6, 7)
+
+                    assertThat(placedItems.values).isInOrder(placementComparator)
+
                     // Return true to stop the search.
                     true
                 }
@@ -298,7 +307,7 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(placedItems.keys).containsExactly(5, 6, 7)
             assertThat(visibleItems).containsExactly(5, 6, 7)
         }
     }
@@ -342,12 +351,14 @@
                 } else {
                     // Assert that the beyond bounds items are present.
                     if (expectedExtraItemsBeforeVisibleBounds()) {
-                        assertThat(placedItems).containsExactly(0, 1, 2, 3, 4, 5, 6, 7)
-                        assertThat(visibleItems).containsExactly(5, 6, 7)
+                        assertThat(placedItems.keys).containsExactly(0, 1, 2, 3, 4, 5, 6, 7)
                     } else {
-                        assertThat(placedItems).containsExactly(5, 6, 7, 8, 9, 10)
-                        assertThat(visibleItems).containsExactly(5, 6, 7)
+                        assertThat(placedItems.keys).containsExactly(5, 6, 7, 8, 9, 10)
                     }
+                    assertThat(visibleItems).containsExactly(5, 6, 7)
+
+                    assertThat(placedItems.values).isInOrder(placementComparator)
+
                     // Return true to end the search.
                     true
                 }
@@ -356,7 +367,7 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(placedItems.keys).containsExactly(5, 6, 7)
         }
     }
 
@@ -391,7 +402,7 @@
             }
         }
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(placedItems.keys).containsExactly(5, 6, 7)
             assertThat(visibleItems).containsExactly(5, 6, 7)
         }
 
@@ -401,15 +412,15 @@
                 beyondBoundsLayoutCount++
                 when (beyondBoundsLayoutDirection) {
                     Left, Right, Above, Below -> {
-                        assertThat(placedItems).containsExactly(5, 6, 7)
+                        assertThat(placedItems.keys).containsExactly(5, 6, 7)
                         assertThat(visibleItems).containsExactly(5, 6, 7)
                     }
                     Before, After -> {
                         if (expectedExtraItemsBeforeVisibleBounds()) {
-                            assertThat(placedItems).containsExactly(4, 5, 6, 7)
+                            assertThat(placedItems.keys).containsExactly(4, 5, 6, 7)
                             assertThat(visibleItems).containsExactly(5, 6, 7)
                         } else {
-                            assertThat(placedItems).containsExactly(5, 6, 7, 8)
+                            assertThat(placedItems.keys).containsExactly(5, 6, 7, 8)
                             assertThat(visibleItems).containsExactly(5, 6, 7)
                         }
                     }
@@ -429,7 +440,7 @@
                     assertThat(beyondBoundsLayoutCount).isEqualTo(1)
 
                     // Assert that the beyond bounds items are removed.
-                    assertThat(placedItems).containsExactly(5, 6, 7)
+                    assertThat(placedItems.keys).containsExactly(5, 6, 7)
                     assertThat(visibleItems).containsExactly(5, 6, 7)
                 }
                 else -> error("Unsupported BeyondBoundsLayoutDirection")
@@ -480,7 +491,7 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(placedItems.keys).containsExactly(5, 6, 7)
             assertThat(visibleItems).containsExactly(5, 6, 7)
         }
     }
@@ -563,35 +574,60 @@
     )
 
     private fun Modifier.trackPlaced(index: Int): Modifier =
-        this then TrackPlacedElement(placedItems, index)
+        this then TrackPlacedElement(index, placedItems)
 }
 
 internal data class TrackPlacedElement(
-    var placedItems: MutableSet<Int>,
-    var index: Int
+    var index: Int,
+    var placedItems: MutableMap<Int, Rect>
 ) : ModifierNodeElement<TrackPlacedNode>() {
-    override fun create() = TrackPlacedNode(placedItems, index)
+    override fun create() = TrackPlacedNode(index, placedItems)
 
     override fun update(node: TrackPlacedNode) {
-        node.placedItems = placedItems
         node.index = index
+        node.placedItems = placedItems
     }
 
     override fun InspectorInfo.inspectableProperties() {
         name = "trackPlaced"
         properties["index"] = index
+        properties["placedItems"] = placedItems
     }
 }
 
 internal class TrackPlacedNode(
-    var placedItems: MutableSet<Int>,
-    var index: Int
+    var index: Int,
+    var placedItems: MutableMap<Int, Rect>
 ) : LayoutAwareModifierNode, Modifier.Node() {
     override fun onPlaced(coordinates: LayoutCoordinates) {
-        placedItems += index
+        placedItems[index] =
+            coordinates.findRootCoordinates().localBoundingBoxOf(coordinates, false)
     }
 
     override fun onDetach() {
-        placedItems -= index
+        placedItems.remove(index)
+    }
+}
+
+internal class PlacementComparator(
+    val beyondBoundsLayoutDirection: BeyondBoundsLayout.LayoutDirection,
+    val layoutDirection: LayoutDirection,
+    val reverseLayout: Boolean
+) : Comparator<Rect> {
+    private fun itemsInReverseOrder() = when (beyondBoundsLayoutDirection) {
+        Above, Below -> reverseLayout
+        else -> if (layoutDirection == Ltr) reverseLayout else !reverseLayout
+    }
+
+    private fun compareOffset(o1: Float, o2: Float): Int {
+        return if (itemsInReverseOrder()) o2.compareTo(o1) else o1.compareTo(o2)
+    }
+
+    override fun compare(o1: Rect?, o2: Rect?): Int {
+        if (o1 == null || o2 == null) return 0
+        return when (beyondBoundsLayoutDirection) {
+            Above, Below -> compareOffset(o1.top, o2.top)
+            else -> compareOffset(o1.left, o2.left)
+        }
     }
 }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridBeyondBoundsTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridBeyondBoundsTest.kt
index 116e9ae..fda112e 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridBeyondBoundsTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridBeyondBoundsTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.list.PlacementComparator
 import androidx.compose.foundation.lazy.list.TrackPlacedElement
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
@@ -25,6 +26,7 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.layout.BeyondBoundsLayout
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Above
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.After
@@ -71,9 +73,11 @@
     private val beyondBoundsLayoutDirection = param.beyondBoundsLayoutDirection
     private val reverseLayout = param.reverseLayout
     private val layoutDirection = param.layoutDirection
-    private val placedItems = mutableSetOf<Int>()
+    private val placedItems = sortedMapOf<Int, Rect>()
     private var beyondBoundsLayout: BeyondBoundsLayout? = null
     private lateinit var lazyStaggeredGridState: LazyStaggeredGridState
+    private val placementComparator =
+        PlacementComparator(beyondBoundsLayoutDirection, layoutDirection, reverseLayout)
 
     companion object {
         @JvmStatic
@@ -104,7 +108,7 @@
 
         // Assert.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(0)
+            assertThat(placedItems.keys).containsExactly(0)
             assertThat(visibleItems).containsExactly(0)
         }
     }
@@ -124,7 +128,7 @@
 
         // Assert.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(0, 1)
+            assertThat(placedItems.keys).containsExactly(0, 1)
             assertThat(visibleItems).containsExactly(0, 1)
         }
     }
@@ -144,7 +148,7 @@
 
         // Assert.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(0, 1, 2)
+            assertThat(placedItems.keys).containsExactly(0, 1, 2)
             assertThat(visibleItems).containsExactly(0, 1, 2)
         }
     }
@@ -217,12 +221,14 @@
             beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
                 // Assert that the beyond bounds items are present.
                 if (expectedExtraItemsBeforeVisibleBounds()) {
-                    assertThat(placedItems).containsExactly(4, 5, 6, 7)
-                    assertThat(visibleItems).containsExactly(5, 6, 7)
+                    assertThat(placedItems.keys).containsExactly(4, 5, 6, 7)
                 } else {
-                    assertThat(placedItems).containsExactly(5, 6, 7, 8)
-                    assertThat(visibleItems).containsExactly(5, 6, 7)
+                    assertThat(placedItems.keys).containsExactly(5, 6, 7, 8)
                 }
+                assertThat(visibleItems).containsExactly(5, 6, 7)
+
+                assertThat(placedItems.values).isInOrder(placementComparator)
+
                 // Just return true so that we stop as soon as we run this once.
                 // This should result in one extra item being added.
                 true
@@ -231,7 +237,7 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(placedItems.keys).containsExactly(5, 6, 7)
             assertThat(visibleItems).containsExactly(5, 6, 7)
         }
     }
@@ -275,12 +281,14 @@
             beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
                 // Assert that the beyond bounds items are present.
                 if (expectedExtraItemsBeforeVisibleBounds()) {
-                    assertThat(placedItems).containsExactly(9, 10, 11, 12, 13, 14, 15)
-                    assertThat(visibleItems).containsExactly(10, 11, 12, 13, 14, 15)
+                    assertThat(placedItems.keys).containsExactly(9, 10, 11, 12, 13, 14, 15)
                 } else {
-                    assertThat(placedItems).containsExactly(10, 11, 12, 13, 14, 15, 16)
-                    assertThat(visibleItems).containsExactly(10, 11, 12, 13, 14, 15)
+                    assertThat(placedItems.keys).containsExactly(10, 11, 12, 13, 14, 15, 16)
                 }
+                assertThat(visibleItems).containsExactly(10, 11, 12, 13, 14, 15)
+
+                assertThat(placedItems.values).isInOrder(placementComparator)
+
                 // Just return true so that we stop as soon as we run this once.
                 // This should result in one extra item being added.
                 true
@@ -289,7 +297,7 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(10, 11, 12, 13, 14, 15)
+            assertThat(placedItems.keys).containsExactly(10, 11, 12, 13, 14, 15)
             assertThat(visibleItems).containsExactly(10, 11, 12, 13, 14, 15)
         }
     }
@@ -341,10 +349,10 @@
             beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
                 // Assert that the beyond bounds items are present.
                 if (expectedExtraItemsBeforeVisibleBounds()) {
-                    assertThat(placedItems).containsExactly(3, 4, 5, 6, 7)
+                    assertThat(placedItems.keys).containsExactly(3, 4, 5, 6, 7)
                     assertThat(visibleItems).containsExactly(4, 5, 6, 7)
                 } else {
-                    assertThat(placedItems).containsExactly(4, 5, 6, 7, 8)
+                    assertThat(placedItems.keys).containsExactly(4, 5, 6, 7, 8)
                     assertThat(visibleItems).containsExactly(4, 5, 6, 7)
                 }
                 // Just return true so that we stop as soon as we run this once.
@@ -355,7 +363,7 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(4, 5, 6, 7)
+            assertThat(placedItems.keys).containsExactly(4, 5, 6, 7)
             assertThat(visibleItems).containsExactly(4, 5, 6, 7)
         }
     }
@@ -400,12 +408,14 @@
                 } else {
                     // Assert that the beyond bounds items are present.
                     if (expectedExtraItemsBeforeVisibleBounds()) {
-                        assertThat(placedItems).containsExactly(3, 4, 5, 6, 7)
-                        assertThat(visibleItems).containsExactly(5, 6, 7)
+                        assertThat(placedItems.keys).containsExactly(3, 4, 5, 6, 7)
                     } else {
-                        assertThat(placedItems).containsExactly(5, 6, 7, 8, 9)
-                        assertThat(visibleItems).containsExactly(5, 6, 7)
+                        assertThat(placedItems.keys).containsExactly(5, 6, 7, 8, 9)
                     }
+                    assertThat(visibleItems).containsExactly(5, 6, 7)
+
+                    assertThat(placedItems.values).isInOrder(placementComparator)
+
                     // Return true to stop the search.
                     true
                 }
@@ -414,7 +424,7 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(placedItems.keys).containsExactly(5, 6, 7)
             assertThat(visibleItems).containsExactly(5, 6, 7)
         }
     }
@@ -458,12 +468,15 @@
                 } else {
                     // Assert that the beyond bounds items are present.
                     if (expectedExtraItemsBeforeVisibleBounds()) {
-                        assertThat(placedItems).containsExactly(0, 1, 2, 3, 4, 5, 6, 7)
-                        assertThat(visibleItems).containsExactly(5, 6, 7)
+                        assertThat(placedItems.keys).containsExactly(0, 1, 2, 3, 4, 5, 6, 7)
                     } else {
-                        assertThat(placedItems).containsExactly(5, 6, 7, 8, 9, 10)
-                        assertThat(visibleItems).containsExactly(5, 6, 7)
+                        assertThat(placedItems.keys).containsExactly(5, 6, 7, 8, 9, 10)
                     }
+                    assertThat(visibleItems).containsExactly(5, 6, 7)
+
+                    // Verify if the placed item offsets are in order.
+                    assertThat(placedItems.toSortedMap().values).isInOrder(placementComparator)
+
                     // Return true to end the search.
                     true
                 }
@@ -472,7 +485,7 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(placedItems.keys).containsExactly(5, 6, 7)
         }
     }
 
@@ -507,7 +520,7 @@
             }
         }
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(placedItems.keys).containsExactly(5, 6, 7)
             assertThat(visibleItems).containsExactly(5, 6, 7)
         }
 
@@ -517,15 +530,15 @@
                 beyondBoundsLayoutCount++
                 when (beyondBoundsLayoutDirection) {
                     Left, Right, Above, Below -> {
-                        assertThat(placedItems).containsExactly(5, 6, 7)
+                        assertThat(placedItems.keys).containsExactly(5, 6, 7)
                         assertThat(visibleItems).containsExactly(5, 6, 7)
                     }
                     Before, After -> {
                         if (expectedExtraItemsBeforeVisibleBounds()) {
-                            assertThat(placedItems).containsExactly(4, 5, 6, 7)
+                            assertThat(placedItems.keys).containsExactly(4, 5, 6, 7)
                             assertThat(visibleItems).containsExactly(5, 6, 7)
                         } else {
-                            assertThat(placedItems).containsExactly(5, 6, 7, 8)
+                            assertThat(placedItems.keys).containsExactly(5, 6, 7, 8)
                             assertThat(visibleItems).containsExactly(5, 6, 7)
                         }
                     }
@@ -545,7 +558,7 @@
                     assertThat(beyondBoundsLayoutCount).isEqualTo(1)
 
                     // Assert that the beyond bounds items are removed.
-                    assertThat(placedItems).containsExactly(5, 6, 7)
+                    assertThat(placedItems.keys).containsExactly(5, 6, 7)
                     assertThat(visibleItems).containsExactly(5, 6, 7)
                 }
                 else -> error("Unsupported BeyondBoundsLayoutDirection")
@@ -596,7 +609,7 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(placedItems.keys).containsExactly(5, 6, 7)
             assertThat(visibleItems).containsExactly(5, 6, 7)
         }
     }
@@ -684,5 +697,5 @@
     )
 
     private fun Modifier.trackPlaced(index: Int): Modifier =
-        this then TrackPlacedElement(placedItems, index)
+        this then TrackPlacedElement(index, placedItems)
 }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerStateNonGestureScrollingTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerStateNonGestureScrollingTest.kt
index 722fb27..ff647ba 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerStateNonGestureScrollingTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerStateNonGestureScrollingTest.kt
@@ -35,6 +35,7 @@
 import com.google.common.truth.Truth
 import kotlin.test.assertFalse
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withContext
@@ -303,6 +304,75 @@
         Truth.assertThat(pagerState.currentPage).isEqualTo(5)
     }
 
+    @Test
+    fun updateCurrentPage_shouldUpdateCurrentPageImmediately() {
+        createPager(modifier = Modifier.fillMaxSize())
+
+        Truth.assertThat(pagerState.currentPage).isEqualTo(0)
+
+        rule.runOnUiThread {
+            scope.launch {
+                with(pagerState) {
+                    scroll {
+                        updateCurrentPage(5)
+                    }
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(pagerState.currentPage).isEqualTo(5)
+        }
+    }
+
+    @Test
+    fun updateCurrentPage_shouldUpdateCurrentPageOffsetFractionImmediately() {
+        createPager(modifier = Modifier.fillMaxSize())
+
+        Truth.assertThat(pagerState.currentPage).isEqualTo(0)
+
+        rule.runOnUiThread {
+            scope.launch {
+                with(pagerState) {
+                    scroll {
+                        updateCurrentPage(5, 0.3f)
+                    }
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(pagerState.currentPageOffsetFraction).isWithin(0.01f).of(0.3f)
+        }
+    }
+
+    @Test
+    fun updateTargetPage_shouldUpdateTargetPageImmediately_andResetIfNotMoved() {
+        createPager(modifier = Modifier.fillMaxSize())
+
+        Truth.assertThat(pagerState.targetPage).isEqualTo(pagerState.currentPage)
+
+        rule.mainClock.autoAdvance = false
+
+        rule.runOnUiThread {
+            scope.launch {
+                with(pagerState) {
+                    scroll {
+                        updateTargetPage(5)
+                        delay(1_000L) // simulate an animation
+                    }
+                }
+            }
+        }
+
+        rule.mainClock.advanceTimeByFrame() // pump a frame
+        Truth.assertThat(pagerState.targetPage).isEqualTo(5) // target page changed
+        rule.mainClock.advanceTimeBy(2_000L) // scroll block finished but we didn't move
+        // target page reset
+        Truth.assertThat(pagerState.currentPage).isEqualTo(0)
+        Truth.assertThat(pagerState.targetPage).isEqualTo(pagerState.currentPage)
+    }
+
     companion object {
         @JvmStatic
         @Parameterized.Parameters(name = "{0}")
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerStateTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerStateTest.kt
index 816340a..1289cf9 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerStateTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerStateTest.kt
@@ -17,13 +17,16 @@
 package androidx.compose.foundation.pager
 
 import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.animate
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.AutoTestFrameClock
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.gestures.scrollBy
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.layout.layout
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performTouchInput
 import androidx.test.filters.LargeTest
@@ -540,6 +543,66 @@
     }
 
     @Test
+    fun targetPage_shouldReflectTargetWithCustomAnimation() {
+        // Arrange
+        suspend fun PagerState.customAnimateScrollToPage(page: Int) {
+            scroll {
+                updateTargetPage(page)
+                val targetPageDiff = page - currentPage
+                val distance = targetPageDiff * layoutInfo.pageSize.toFloat()
+                var previousValue = 0.0f
+                animate(
+                    0f,
+                    distance,
+                ) { currentValue, _ ->
+                    previousValue += scrollBy(currentValue - previousValue)
+                }
+            }
+        }
+
+        createPager(
+            modifier = Modifier.fillMaxSize()
+        )
+        rule.runOnIdle { assertThat(pagerState.targetPage).isEqualTo(pagerState.currentPage) }
+
+        rule.mainClock.autoAdvance = false
+        // Act
+        // Moving forward
+        var previousTarget = pagerState.targetPage
+        rule.runOnIdle {
+            scope.launch {
+                pagerState.customAnimateScrollToPage(DefaultPageCount - 1)
+            }
+        }
+        rule.mainClock.advanceTimeUntil { pagerState.targetPage != previousTarget }
+
+        // Assert
+        assertThat(pagerState.targetPage).isEqualTo(DefaultPageCount - 1)
+        assertThat(pagerState.targetPage).isNotEqualTo(pagerState.currentPage)
+
+        rule.mainClock.autoAdvance = true
+        rule.runOnIdle { assertThat(pagerState.targetPage).isEqualTo(pagerState.currentPage) }
+        rule.mainClock.autoAdvance = false
+
+        // Act
+        // Moving backward
+        previousTarget = pagerState.targetPage
+        rule.runOnIdle {
+            scope.launch {
+                pagerState.customAnimateScrollToPage(0)
+            }
+        }
+        rule.mainClock.advanceTimeUntil { pagerState.targetPage != previousTarget }
+
+        // Assert
+        assertThat(pagerState.targetPage).isEqualTo(0)
+        assertThat(pagerState.targetPage).isNotEqualTo(pagerState.currentPage)
+
+        rule.mainClock.autoAdvance = true
+        rule.runOnIdle { assertThat(pagerState.targetPage).isEqualTo(pagerState.currentPage) }
+    }
+
+    @Test
     fun targetPage_valueAfterScrollingAfterMidpoint() {
         createPager(initialPage = 5, modifier = Modifier.fillMaxSize())
 
@@ -757,6 +820,32 @@
         }
     }
 
+    @Test
+    fun onScroll_shouldNotGenerateExtraMeasurements() {
+        // Arrange
+        var layoutCount = 0
+        createPager(initialPage = 5, modifier = Modifier.layout { measurable, constraints ->
+            layoutCount++
+            val placeables = measurable.measure(constraints)
+            layout(constraints.maxWidth, constraints.maxHeight) {
+                placeables.place(0, 0)
+            }
+        })
+
+        // Act: Scroll.
+        val previousMeasurementCount = layoutCount
+        val previousOffsetFraction = pagerState.currentPageOffsetFraction
+        rule.runOnIdle {
+            runBlocking {
+                pagerState.scrollBy((pageSize * 0.2f) * scrollForwardSign)
+            }
+        }
+        rule.runOnIdle {
+            assertThat(pagerState.currentPageOffsetFraction).isNotEqualTo(previousOffsetFraction)
+            assertThat(layoutCount).isEqualTo(previousMeasurementCount + 1)
+        }
+    }
+
     companion object {
         @JvmStatic
         @Parameterized.Parameters(name = "{0}")
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/modifiers/SelectionControllerTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/modifiers/SelectionControllerTest.kt
index 905a598..86e71928b 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/modifiers/SelectionControllerTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/modifiers/SelectionControllerTest.kt
@@ -18,38 +18,38 @@
 
 import android.os.Build
 import androidx.annotation.RequiresApi
-import androidx.compose.foundation.Canvas
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.text.ceilToIntPx
-import androidx.compose.foundation.text.selection.Selectable
-import androidx.compose.foundation.text.selection.Selection
-import androidx.compose.foundation.text.selection.Selection.AnchorInfo
-import androidx.compose.foundation.text.selection.SelectionAdjustment
-import androidx.compose.foundation.text.selection.SelectionLayoutBuilder
-import androidx.compose.foundation.text.selection.SelectionRegistrar
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.foundation.text.selection.LocalTextSelectionColors
+import androidx.compose.foundation.text.selection.SelectionContainer
+import androidx.compose.foundation.text.selection.TextSelectionColors
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.drawBehind
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.Path
 import androidx.compose.ui.graphics.asAndroidBitmap
 import androidx.compose.ui.graphics.toArgb
-import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.getBoundsInRoot
 import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onRoot
-import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.style.ResolvedTextDirection
+import androidx.compose.ui.test.longClick
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.roundToIntRect
+import androidx.compose.ui.unit.sp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
-import org.junit.Assert
+import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -61,223 +61,80 @@
     @get:Rule
     val rule = createComposeRule()
 
+    private val boxTag = "boxTag"
+    private val tag = "tag"
+
+    private val highlightColor = Color.Blue
+    private val foregroundColor = Color.Black
+    private val backgroundColor = Color.White
+
+    private val highlightArgb get() = highlightColor.toArgb()
+    private val foregroundArgb get() = foregroundColor.toArgb()
+    private val backgroundArgb get() = backgroundColor.toArgb()
+
+    private val density = Density(1f)
+    private val textSelectionColors = TextSelectionColors(highlightColor, highlightColor)
+
     @Test
     @SdkSuppress(minSdkVersion = 26)
-    fun drawWithClip_doesClip() {
-        val canvasSize = 10.dp
-        val pathSize = 10_000f
-        val path = Path().also {
-            it.addRect(Rect(0f, 0f, pathSize, pathSize))
-        }
-
-        val fixedSelectionFake = FixedSelectionFake(0, 1000, 200)
-        val subject = SelectionController(
-            selectableId = fixedSelectionFake.nextSelectableId(),
-            selectionRegistrar = fixedSelectionFake,
-            backgroundSelectionColor = Color.White,
-            params = FakeParams(
-                path, true
-            )
-        )
-        var size: Size? = null
-
-        rule.setContent {
-            Box(
-                Modifier
-                    .fillMaxSize()
-                    .drawBehind { drawRect(Color.Black) }) {
-                Canvas(Modifier.size(canvasSize)) {
-                    size = this.size
-                    subject.draw(this)
-                }
-            }
-        }
-
-        rule.waitForIdle()
-        assertClipped(size!!, true)
+    fun drawWithClip_doesClip() = runDrawWithClipTest(TextOverflow.Clip) { argbSet ->
+        assertThat(argbSet).containsExactly(backgroundArgb)
     }
 
     @Test
     @SdkSuppress(minSdkVersion = 26)
-    fun drawWithOut_doesNotClip() {
-        val canvasSize = 10.dp
-        val pathSize = 10_000f
-        val path = Path().also {
-            it.addRect(Rect(0f, 0f, pathSize, pathSize))
-        }
-
-        val fixedSelectionFake = FixedSelectionFake(0, 1000, 200)
-        val subject = SelectionController(
-            selectableId = fixedSelectionFake.nextSelectableId(),
-            selectionRegistrar = fixedSelectionFake,
-            backgroundSelectionColor = Color.White,
-            params = FakeParams(
-                path, false
-            )
-        )
-        var size: Size? = null
-
-        rule.setContent {
-            Box(
-                Modifier
-                    .fillMaxSize()
-                    .drawBehind { drawRect(Color.Black) }) {
-                Canvas(Modifier.size(canvasSize)) {
-                    size = this.size
-                    drawRect(Color.Black)
-                    subject.draw(this)
-                }
-            }
-        }
-
-        rule.waitForIdle()
-        assertClipped(size!!, false)
+    fun drawWithVisible_doesNotClip() = runDrawWithClipTest(TextOverflow.Visible) { argbSet ->
+        // there could be more colors due to anti-aliasing, so check that we have at least the
+        // expected colors, and none of the unexpected colors.
+        assertThat(argbSet).containsAtLeast(highlightArgb, foregroundArgb)
+        assertThat(argbSet).doesNotContain(backgroundArgb)
     }
 
     @RequiresApi(Build.VERSION_CODES.O)
-    private fun assertClipped(size: Size, isClipped: Boolean) {
-        val expectedColor = if (isClipped) { Color.Black } else { Color.White }
-        rule.onRoot().captureToImage().asAndroidBitmap().apply {
-            Assert.assertEquals(
-                expectedColor.toArgb(),
-                getPixel(
-                    size.width.ceilToIntPx() + 5,
-                    size.height.ceilToIntPx() + 5
-                )
-            )
+    private fun runDrawWithClipTest(overflow: TextOverflow, assertBlock: (Set<Int>) -> Unit) {
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalTextSelectionColors provides textSelectionColors,
+                LocalDensity provides density,
+            ) {
+                Box(
+                    modifier = Modifier
+                        .fillMaxSize()
+                        .drawBehind { drawRect(backgroundColor) }
+                        .testTag(boxTag),
+                    contentAlignment = Alignment.Center,
+                ) {
+                    SelectionContainer {
+                        BasicText(
+                            modifier = Modifier
+                                .width(10.dp)
+                                .testTag(tag),
+                            text = "OOOOOOO",
+                            overflow = overflow,
+                            softWrap = false,
+                            style = TextStyle(color = foregroundColor, fontSize = 48.sp),
+                        )
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+        rule.onNodeWithTag(tag).performTouchInput { longClick() }
+        rule.waitForIdle()
+
+        with(density) {
+            val bitmap = rule.onNodeWithTag(boxTag).captureToImage().asAndroidBitmap()
+            val bitmapPositionInRoot =
+                rule.onNodeWithTag(boxTag).getBoundsInRoot().toRect().roundToIntRect().topLeft
+            val centerRightOffset =
+                rule.onNodeWithTag(tag).getBoundsInRoot().toRect().roundToIntRect().centerRight
+
+            val centerRightOffsetInRoot = centerRightOffset - bitmapPositionInRoot
+            val (x, y) = centerRightOffsetInRoot
+
+            // grab a row of pixels in the area that may be clipped.
+            val seenColors = (1..50).map { bitmap.getPixel(x + it, y) }.toSet()
+            assertBlock(seenColors)
         }
     }
 }
-
-/**
- * Fake that always has selection
- */
-private class FixedSelectionFake(
-    val start: Int,
-    val end: Int,
-    val lastVisible: Int
-) : SelectionRegistrar {
-
-    var selectableId = 0L
-    var allSelectables = mutableListOf<Long>()
-
-    override val subselections: Map<Long, Selection>
-        get() = allSelectables.associateWith { selectionId ->
-            Selection(
-                AnchorInfo(ResolvedTextDirection.Ltr, start, selectionId),
-                AnchorInfo(ResolvedTextDirection.Ltr, end, selectionId)
-            )
-        }
-
-    override fun subscribe(selectable: Selectable): Selectable {
-        return FakeSelectableWithLastVisibleOffset(selectable.selectableId, lastVisible)
-    }
-
-    override fun unsubscribe(selectable: Selectable) {
-        // nothing
-    }
-
-    override fun nextSelectableId(): Long {
-        return selectableId++.also {
-            allSelectables.add(it)
-        }
-    }
-
-    override fun notifyPositionChange(selectableId: Long) {
-        FAKE("Not yet implemented")
-    }
-
-    override fun notifySelectionUpdateStart(
-        layoutCoordinates: LayoutCoordinates,
-        startPosition: Offset,
-        adjustment: SelectionAdjustment,
-        isInTouchMode: Boolean
-    ) {
-        FAKE("Selection not editable")
-    }
-
-    override fun notifySelectionUpdateSelectAll(selectableId: Long, isInTouchMode: Boolean) {
-        FAKE()
-    }
-
-    override fun notifySelectionUpdate(
-        layoutCoordinates: LayoutCoordinates,
-        newPosition: Offset,
-        previousPosition: Offset,
-        isStartHandle: Boolean,
-        adjustment: SelectionAdjustment,
-        isInTouchMode: Boolean
-    ): Boolean {
-        FAKE("Selection not editable")
-    }
-
-    override fun notifySelectionUpdateEnd() {
-        FAKE("Selection not editable")
-    }
-
-    override fun notifySelectableChange(selectableId: Long) {
-        FAKE("Selection not editable")
-    }
-}
-
-private class FakeSelectableWithLastVisibleOffset(
-    override val selectableId: Long,
-    private val lastVisible: Int
-) : Selectable {
-    override fun appendSelectableInfoToBuilder(builder: SelectionLayoutBuilder) {
-        FAKE()
-    }
-
-    override fun getSelectAllSelection(): Selection? {
-        FAKE()
-    }
-
-    override fun getHandlePosition(selection: Selection, isStartHandle: Boolean): Offset {
-        FAKE()
-    }
-
-    override fun getLayoutCoordinates(): LayoutCoordinates? {
-        FAKE()
-    }
-
-    override fun getText(): AnnotatedString {
-        FAKE()
-    }
-
-    override fun getBoundingBox(offset: Int): Rect {
-        FAKE()
-    }
-
-    override fun getLineLeft(offset: Int): Float {
-        FAKE()
-    }
-
-    override fun getLineRight(offset: Int): Float {
-        FAKE()
-    }
-
-    override fun getCenterYForOffset(offset: Int): Float {
-        FAKE()
-    }
-
-    override fun getRangeOfLineContaining(offset: Int): TextRange {
-        FAKE()
-    }
-
-    override fun getLastVisibleOffset(): Int {
-        return lastVisible
-    }
-}
-
-private class FakeParams(
-    val path: Path,
-    override val shouldClip: Boolean
-) : StaticTextSelectionParams(null, null) {
-
-    override fun getPathForRange(start: Int, end: Int): Path? {
-        return path
-    }
-}
-
-private fun FAKE(reason: String = "Unsupported fake method on fake"): Nothing =
-    throw NotImplementedError("No support in fake: $reason")
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/LazyColumnMultiTextRegressionTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/LazyColumnMultiTextRegressionTest.kt
index d78a009..4aa35e8 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/LazyColumnMultiTextRegressionTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/selection/gestures/LazyColumnMultiTextRegressionTest.kt
@@ -16,27 +16,36 @@
 
 package androidx.compose.foundation.text.selection.gestures
 
+import androidx.compose.foundation.layout.Box
 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.Handle
 import androidx.compose.foundation.text.selection.Selection
 import androidx.compose.foundation.text.selection.SelectionContainer
+import androidx.compose.foundation.text.selection.SelectionHandleInfoKey
 import androidx.compose.foundation.text.selection.fetchTextLayoutResult
 import androidx.compose.foundation.text.selection.gestures.util.longPress
+import androidx.compose.foundation.text.selection.isSelectionHandle
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.testutils.TestViewConfiguration
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.input.key.Key
 import androidx.compose.ui.platform.ClipboardManager
 import androidx.compose.ui.platform.LocalClipboardManager
+import androidx.compose.ui.platform.LocalTextToolbar
 import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.platform.TextToolbar
+import androidx.compose.ui.platform.TextToolbarStatus
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.TouchInjectionScope
@@ -64,6 +73,7 @@
     @get:Rule
     val rule = createComposeRule()
     private val stateRestorationTester = StateRestorationTester(rule)
+    private val textCount = 20
 
     // regression - text going out of composition and then returning
     // resulted in selection not working
@@ -98,6 +108,84 @@
         assertClipboardTextEquals("01234")
     }
 
+    @Test
+    fun whenScrollingTextOutOfViewUpwards_handlesDisappear() = runTest {
+        var prevStart: Offset? = null
+        var prevEnd: Offset? = null
+
+        fun updateHandlePositions() {
+            prevStart = startHandlePosition
+            prevEnd = endHandlePosition
+        }
+
+        assertHandleNotShown(Handle.SelectionStart)
+        assertHandleNotShown(Handle.SelectionEnd)
+
+        createSelection(startLine = 1, endLine = 3)
+        assertHandleShown(Handle.SelectionStart)
+        assertHandleShown(Handle.SelectionEnd)
+        updateHandlePositions()
+
+        scrollLines(fromLine = 3, toLine = 2)
+        assertHandleShown(Handle.SelectionStart)
+        assertHandleShown(Handle.SelectionEnd)
+        assertPositionMovedUp(prevStart, startHandlePosition)
+        assertPositionMovedUp(prevEnd, endHandlePosition)
+        updateHandlePositions()
+
+        scrollLines(fromLine = 4, toLine = 2)
+        assertHandleNotShown(Handle.SelectionStart)
+        assertHandleShown(Handle.SelectionEnd)
+        assertPositionMovedUp(prevEnd, endHandlePosition)
+
+        scrollLines(fromLine = 6, toLine = 4)
+        assertHandleNotShown(Handle.SelectionStart)
+        assertHandleNotShown(Handle.SelectionEnd)
+        updateHandlePositions()
+
+        scrollLines(fromLine = 6, toLine = 8)
+        assertHandleNotShown(Handle.SelectionStart)
+        assertHandleShown(Handle.SelectionEnd)
+        updateHandlePositions()
+
+        scrollLines(fromLine = 4, toLine = 6)
+        assertHandleShown(Handle.SelectionStart)
+        assertHandleShown(Handle.SelectionEnd)
+        assertHandleMovedDown(prevEnd, endHandlePosition)
+        updateHandlePositions()
+
+        scrollLines(fromLine = 2, toLine = 5)
+        assertHandleShown(Handle.SelectionStart)
+        assertHandleShown(Handle.SelectionEnd)
+        assertHandleMovedDown(prevStart, startHandlePosition)
+        assertHandleMovedDown(prevEnd, endHandlePosition)
+        updateHandlePositions()
+    }
+
+    @Test
+    fun whenScrollingTextOutOfViewUpwards_textToolbarCoercedToTop() = runTest {
+        assertThat(textToolbarShown).isFalse()
+
+        createSelection(startLine = 1, endLine = 3)
+        assertThat(textToolbarShown).isTrue()
+        assertTextToolbarTopAt(boundingBoxForLineInRoot(1).top)
+
+        scrollLines(fromLine = 3, toLine = 1)
+        assertThat(textToolbarShown).isTrue()
+        assertTextToolbarTopAt(pointerAreaRect.top)
+
+        scrollLines(fromLine = 5, toLine = 3)
+        assertThat(textToolbarShown).isFalse()
+
+        scrollLines(fromLine = 5, toLine = 7)
+        assertThat(textToolbarShown).isTrue()
+        assertTextToolbarTopAt(pointerAreaRect.top)
+
+        scrollLines(fromLine = 3, toLine = 5)
+        assertThat(textToolbarShown).isTrue()
+        assertTextToolbarTopAt(boundingBoxForLineInRoot(1).top)
+    }
+
     // TODO(b/298067619)
     //  When we support saving selection, this test should instead check that
     //  the previous and current selection is the same.
@@ -114,21 +202,27 @@
         private val pointerAreaTag: String,
         private val selectionState: MutableState<Selection?>,
         private val clipboardManager: ClipboardManager,
+        private val textToolbar: TextToolbarWrapper,
     ) {
         val initialText = "Initial text"
-        val selection get() = Snapshot.withoutReadObservation { selectionState.value }
+        val selection: Selection? get() = Snapshot.withoutReadObservation { selectionState.value }
+        val textToolbarRect: Rect? get() = textToolbar.mostRecentRect
+        val textToolbarShown: Boolean get() = textToolbar.shown
+
+        val startHandlePosition get() = handlePosition(Handle.SelectionStart)
+        val endHandlePosition get() = handlePosition(Handle.SelectionEnd)
 
         fun createSelection(startLine: Int, endLine: Int) {
             performTouchInput {
-                longPress(positionForLine(startLine))
-                moveTo(positionForLine(endLine))
+                longPress(positionForLineInPointerArea(startLine))
+                moveTo(positionForLineInPointerArea(endLine))
                 up()
             }
         }
 
         fun createSelection(line: Int) {
             performTouchInput {
-                longClick(positionForLine(line))
+                longClick(positionForLineInPointerArea(line))
             }
         }
 
@@ -137,25 +231,38 @@
             rule.waitForIdle()
         }
 
-        private fun positionForLine(lineNumber: Int): Offset {
+        fun boundingBoxForLineInPointerArea(lineNumber: Int): Rect {
             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) }
+            return boundingBoxForLineInRoot(lineNumber).translate(-containerPosition)
         }
 
+        fun boundingBoxForLineInRoot(lineNumber: Int): Rect {
+            val textTag = lineNumber.toString()
+            val textPosition = rule.onNodeWithTag(textTag).fetchSemanticsNode().positionInRoot
+            val textLayoutResult = rule.onNodeWithTag(textTag).fetchTextLayoutResult()
+            val lineStart = textLayoutResult.getLineStart(0)
+            val lineEnd = textLayoutResult.getLineEnd(0)
+
+            val rect = if (lineStart == lineEnd - 1) {
+                textLayoutResult.getBoundingBox(lineStart)
+            } else {
+                val startRect = textLayoutResult.getBoundingBox(lineStart)
+                val endRect = textLayoutResult.getBoundingBox(lineEnd - 1)
+                Rect(
+                    left = minOf(startRect.left, endRect.left),
+                    top = minOf(startRect.top, endRect.top),
+                    right = maxOf(startRect.right, endRect.right),
+                    bottom = maxOf(startRect.bottom, endRect.bottom),
+                )
+            }
+
+            return rect.translate(textPosition)
+        }
+
+        fun positionForLineInPointerArea(lineNumber: Int): Offset =
+            boundingBoxForLineInPointerArea(lineNumber).center
+
         @OptIn(ExperimentalTestApi::class)
         fun performCopy() {
             rule.onNodeWithTag(pointerAreaTag).performKeyInput {
@@ -195,6 +302,69 @@
                 swipe(topCenter + Offset(0f, 1f), bottomCenter - Offset(0f, 1f))
             }
         }
+
+        fun scrollLines(fromLine: Int, toLine: Int) {
+            performTouchInput {
+                swipe(positionForLineInPointerArea(fromLine), positionForLineInPointerArea(toLine))
+            }
+        }
+
+        fun assertPositionMovedUp(previous: Offset?, current: Offset?) {
+            assertHandleMoved(previous, current, up = true)
+        }
+
+        fun assertHandleMovedDown(previous: Offset?, current: Offset?) {
+            assertHandleMoved(previous, current, up = false)
+        }
+
+        private fun assertHandleMoved(previous: Offset?, current: Offset?, up: Boolean) {
+            assertWithMessage("previous handle position should not be null")
+                .that(previous)
+                .isNotNull()
+
+            assertWithMessage("current handle position should not be null")
+                .that(current)
+                .isNotNull()
+
+            val (x, y) = current!!
+            val (prevX, prevY) = previous!!
+
+            assertWithMessage("x should not change")
+                .that(x)
+                .isWithin(0.1f)
+                .of(prevX)
+
+            assertWithMessage("y should have moved ${if (up) "up" else "down"}")
+                .that(y)
+                .run {
+                    if (up) isLessThan(prevY) else isGreaterThan(prevY)
+                }
+        }
+
+        private fun handlePosition(handle: Handle): Offset? =
+            rule.onAllNodes(isSelectionHandle(handle))
+                .fetchSemanticsNodes()
+                .singleOrNull()
+                ?.config
+                ?.get(SelectionHandleInfoKey)
+                ?.position
+
+        fun assertHandleShown(handle: Handle) {
+            rule.onNode(isSelectionHandle(handle)).assertExists()
+        }
+
+        fun assertHandleNotShown(handle: Handle) {
+            rule.onNode(isSelectionHandle(handle)).assertDoesNotExist()
+        }
+
+        fun assertTextToolbarTopAt(y: Float) {
+            assertThat(textToolbarRect?.top)
+                .isWithin(0.1f)
+                .of(y)
+        }
+
+        val pointerAreaRect: Rect
+            get() = rule.onNodeWithTag(pointerAreaTag).fetchSemanticsNode().boundsInRoot
     }
 
     private fun runTest(block: TestScope.() -> Unit) {
@@ -204,38 +374,83 @@
             minimumTouchTargetSize = DpSize.Zero
         )
         lateinit var clipboardManager: ClipboardManager
+        lateinit var textToolbar: TextToolbarWrapper
         stateRestorationTester.setContent {
             clipboardManager = LocalClipboardManager.current
-            CompositionLocalProvider(LocalViewConfiguration provides testViewConfiguration) {
-                SelectionContainer(
-                    selection = selection.value,
-                    onSelectionChange = { selection.value = it },
-                    modifier = Modifier
-                        .fillMaxSize()
-                        .wrapContentHeight()
+            val originalTextToolbar = LocalTextToolbar.current
+            textToolbar = remember(originalTextToolbar) {
+                TextToolbarWrapper(originalTextToolbar)
+            }
+            CompositionLocalProvider(
+                LocalTextToolbar provides textToolbar,
+                LocalViewConfiguration provides testViewConfiguration,
+            ) {
+                Box(
+                    modifier = Modifier.fillMaxSize(),
+                    contentAlignment = Alignment.Center,
                 ) {
-                    LazyColumn(
-                        modifier = Modifier
-                            .height(100.dp)
-                            .wrapContentHeight()
-                            .testTag(tag)
+                    SelectionContainer(
+                        modifier = Modifier.height(100.dp),
+                        selection = selection.value,
+                        onSelectionChange = { selection.value = it },
                     ) {
-                        items(count = 20) {
-                            BasicText(
-                                text = it.toString(),
-                                style = TextStyle(fontSize = 15.sp, textAlign = TextAlign.Center),
-                                modifier = Modifier
-                                    .fillMaxWidth()
-                                    .testTag(it.toString())
-                            )
+                        LazyColumn(
+                            modifier = Modifier.testTag(tag)
+                        ) {
+                            items(count = textCount) {
+                                BasicText(
+                                    text = it.toString(),
+                                    style = TextStyle(
+                                        fontSize = 15.sp,
+                                        textAlign = TextAlign.Center
+                                    ),
+                                    modifier = Modifier
+                                        .fillMaxWidth()
+                                        .testTag(it.toString())
+                                )
+                            }
                         }
                     }
                 }
             }
         }
 
-        val scope = TestScope(tag, selection, clipboardManager)
+        val scope = TestScope(tag, selection, clipboardManager, textToolbar)
         scope.resetClipboard()
         scope.block()
     }
 }
+
+private class TextToolbarWrapper(private val delegate: TextToolbar) : TextToolbar {
+    private var _shown: Boolean = false
+    val shown: Boolean get() = _shown
+
+    private var _mostRecentRect: Rect? = null
+    val mostRecentRect: Rect? get() = _mostRecentRect
+
+    override fun showMenu(
+        rect: Rect,
+        onCopyRequested: (() -> Unit)?,
+        onPasteRequested: (() -> Unit)?,
+        onCutRequested: (() -> Unit)?,
+        onSelectAllRequested: (() -> Unit)?
+    ) {
+        _shown = true
+        _mostRecentRect = rect
+        delegate.showMenu(
+            rect,
+            onCopyRequested,
+            onPasteRequested,
+            onCutRequested,
+            onSelectAllRequested
+        )
+    }
+
+    override fun hide() {
+        _shown = false
+        delegate.hide()
+    }
+
+    override val status: TextToolbarStatus
+        get() = delegate.status
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/BasicSecureTextFieldTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/BasicSecureTextFieldTest.kt
index 0def91c..44c013b 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/BasicSecureTextFieldTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/BasicSecureTextFieldTest.kt
@@ -33,6 +33,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.key.Key
 import androidx.compose.ui.platform.LocalClipboardManager
 import androidx.compose.ui.platform.LocalTextToolbar
 import androidx.compose.ui.platform.testTag
@@ -45,10 +46,12 @@
 import androidx.compose.ui.test.assertTextEquals
 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.performTextReplacement
+import androidx.compose.ui.test.pressKey
 import androidx.compose.ui.test.requestFocus
 import androidx.compose.ui.text.TextRange
 import androidx.compose.ui.text.input.TextFieldValue
@@ -57,6 +60,7 @@
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -64,7 +68,7 @@
 @OptIn(ExperimentalFoundationApi::class, ExperimentalTestApi::class)
 @MediumTest
 @RunWith(AndroidJUnit4::class)
-class BasicSecureTextFieldTest {
+internal class BasicSecureTextFieldTest {
 
     // Keyboard shortcut tests for BasicSecureTextField are in TextFieldKeyEventTest
 
@@ -74,9 +78,18 @@
     }
 
     @get:Rule
+    val immRule = ComposeInputMethodManagerTestRule()
+
+    @get:Rule
     val inputMethodInterceptor = InputMethodInterceptorRule(rule)
 
     private val Tag = "BasicSecureTextField"
+    private val imm = FakeInputMethodManager()
+
+    @Before
+    fun setUp() {
+        immRule.setFactory { imm }
+    }
 
     @Test
     fun passwordSemanticsAreSet() {
@@ -665,6 +678,32 @@
         }
     }
 
+    @Test
+    fun inputMethod_doesNotRestart_inResponseToKeyEvents() {
+        val state = TextFieldState("hello", initialSelectionInChars = TextRange(5))
+        rule.setContent {
+            BasicSecureTextField(
+                state = state,
+                modifier = Modifier.testTag(Tag)
+            )
+        }
+
+        with(rule.onNodeWithTag(Tag)) {
+            requestFocus()
+            imm.resetCalls()
+
+            performKeyInput { pressKey(Key.Backspace) }
+            performTextInputSelection(TextRange.Zero)
+            performKeyInput { pressKey(Key.Delete) }
+        }
+
+        rule.runOnIdle {
+            imm.expectCall("updateSelection(4, 4, -1, -1)")
+            imm.expectCall("updateSelection(0, 0, -1, -1)")
+            imm.expectNoMoreCalls()
+        }
+    }
+
     private fun requestFocus(tag: String) =
         rule.onNodeWithTag(tag).requestFocus()
 
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/BasicTextField2ImmIntegrationTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/BasicTextField2ImmIntegrationTest.kt
index 59017e0..3ebfdee 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/BasicTextField2ImmIntegrationTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/BasicTextField2ImmIntegrationTest.kt
@@ -16,11 +16,12 @@
 
 package androidx.compose.foundation.text2
 
-import android.view.KeyEvent
-import android.view.inputmethod.ExtractedText
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.text2.input.InputTransformation
+import androidx.compose.foundation.text2.input.TextFieldBuffer
+import androidx.compose.foundation.text2.input.TextFieldCharSequence
 import androidx.compose.foundation.text2.input.TextFieldState
-import androidx.compose.foundation.text2.input.internal.ComposeInputMethodManager
 import androidx.compose.foundation.text2.input.placeCursorAtEnd
 import androidx.compose.foundation.text2.input.selectAll
 import androidx.compose.runtime.getValue
@@ -38,6 +39,7 @@
 import androidx.compose.ui.test.pressKey
 import androidx.compose.ui.test.requestFocus
 import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.input.KeyboardType
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
@@ -331,52 +333,64 @@
         }
     }
 
-    private fun requestFocus(tag: String) =
-        rule.onNodeWithTag(tag).requestFocus()
-
-    private class FakeInputMethodManager : ComposeInputMethodManager {
-        private val calls = mutableListOf<String>()
-
-        fun expectCall(description: String) {
-            assertThat(calls.removeFirst()).isEqualTo(description)
+    @Test
+    fun immNotRestarted_whenKeyboardIsConfiguredAsPassword() {
+        val state = TextFieldState()
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
+            )
         }
+        requestFocus(Tag)
+        rule.runOnIdle { imm.resetCalls() }
 
-        fun expectNoMoreCalls() {
-            assertThat(calls).isEmpty()
-        }
+        rule.onNodeWithTag(Tag).performKeyInput { pressKey(Key.A) }
+        rule.onNodeWithTag(Tag).performKeyInput { pressKey(Key.Backspace) }
 
-        fun resetCalls() {
-            calls.clear()
-        }
-
-        override fun restartInput() {
-            calls += "restartInput"
-        }
-
-        override fun showSoftInput() {
-            calls += "showSoftInput"
-        }
-
-        override fun hideSoftInput() {
-            calls += "hideSoftInput"
-        }
-
-        override fun updateExtractedText(token: Int, extractedText: ExtractedText) {
-            calls += "updateExtractedText"
-        }
-
-        override fun updateSelection(
-            selectionStart: Int,
-            selectionEnd: Int,
-            compositionStart: Int,
-            compositionEnd: Int
-        ) {
-            calls += "updateSelection($selectionStart, $selectionEnd, " +
-                "$compositionStart, $compositionEnd)"
-        }
-
-        override fun sendKeyEvent(event: KeyEvent) {
-            calls += "sendKeyEvent"
+        rule.runOnIdle {
+            imm.expectCall("updateSelection(1, 1, -1, -1)")
+            imm.expectCall("updateSelection(0, 0, -1, -1)")
+            imm.expectNoMoreCalls()
         }
     }
+
+    @Test
+    fun immNotRestarted_whenKeyboardIsConfiguredAsPassword_fromTransformation() {
+        val state = TextFieldState()
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.testTag(Tag),
+                inputTransformation = object : InputTransformation {
+                    override val keyboardOptions: KeyboardOptions =
+                        KeyboardOptions(keyboardType = KeyboardType.Password)
+
+                    override fun transformInput(
+                        originalValue: TextFieldCharSequence,
+                        valueWithChanges: TextFieldBuffer
+                    ) {
+                        valueWithChanges.append('A')
+                    }
+                }
+            )
+        }
+        requestFocus(Tag)
+        rule.runOnIdle { imm.resetCalls() }
+
+        // "" -key-> "A" -filter> "AA"
+        rule.onNodeWithTag(Tag).performKeyInput { pressKey(Key.A) }
+        // "AA" -key-> "A" -filter> "AA"
+        rule.onNodeWithTag(Tag).performKeyInput { pressKey(Key.Backspace) }
+
+        rule.runOnIdle {
+            imm.expectCall("updateSelection(2, 2, -1, -1)")
+            imm.expectCall("updateSelection(2, 2, -1, -1)")
+            imm.expectNoMoreCalls()
+        }
+    }
+
+    private fun requestFocus(tag: String) =
+        rule.onNodeWithTag(tag).requestFocus()
 }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/FakeInputMethodManager.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/FakeInputMethodManager.kt
new file mode 100644
index 0000000..b9913d5
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/FakeInputMethodManager.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.compose.foundation.text2
+
+import android.view.KeyEvent
+import android.view.inputmethod.ExtractedText
+import androidx.compose.foundation.text2.input.internal.ComposeInputMethodManager
+import com.google.common.truth.Truth.assertThat
+
+internal class FakeInputMethodManager : ComposeInputMethodManager {
+    private val calls = mutableListOf<String>()
+
+    fun expectCall(description: String) {
+        assertThat(calls.removeFirst()).isEqualTo(description)
+    }
+
+    fun expectNoMoreCalls() {
+        assertThat(calls).isEmpty()
+    }
+
+    fun resetCalls() {
+        calls.clear()
+    }
+
+    override fun restartInput() {
+        calls += "restartInput"
+    }
+
+    override fun showSoftInput() {
+        calls += "showSoftInput"
+    }
+
+    override fun hideSoftInput() {
+        calls += "hideSoftInput"
+    }
+
+    override fun updateExtractedText(token: Int, extractedText: ExtractedText) {
+        calls += "updateExtractedText"
+    }
+
+    override fun updateSelection(
+        selectionStart: Int,
+        selectionEnd: Int,
+        compositionStart: Int,
+        compositionEnd: Int
+    ) {
+        calls += "updateSelection($selectionStart, $selectionEnd, " +
+            "$compositionStart, $compositionEnd)"
+    }
+
+    override fun sendKeyEvent(event: KeyEvent) {
+        calls += "sendKeyEvent"
+    }
+}
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/TextFieldKeyEventTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/TextFieldKeyEventTest.kt
index 9af189a..19e905b 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/TextFieldKeyEventTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/TextFieldKeyEventTest.kt
@@ -629,6 +629,36 @@
         }
     }
 
+    @Test
+    fun textField_simpleUndo() {
+        keysSequenceTest("hello") {
+            press(Key.CtrlLeft + Key.DirectionRight)
+            pressKey(Key.Spacebar)
+            pressKey(Key.A)
+            pressKey(Key.B)
+            pressKey(Key.C)
+            expectedText("hello abc")
+            press(Key.CtrlLeft + Key.Z)
+            expectedText("hello")
+        }
+    }
+
+    @Test
+    fun textField_simpleRedo() {
+        keysSequenceTest("hello") {
+            press(Key.CtrlLeft + Key.DirectionRight)
+            pressKey(Key.Spacebar)
+            pressKey(Key.A)
+            pressKey(Key.B)
+            pressKey(Key.C)
+            expectedText("hello abc")
+            press(Key.CtrlLeft + Key.Z)
+            expectedText("hello")
+            press(Key.CtrlLeft + Key.ShiftLeft + Key.Z)
+            expectedText("hello abc")
+        }
+    }
+
     private inner class SequenceScope(
         val state: TextFieldState,
         val clipboardManager: ClipboardManager,
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/undo/BasicTextField2UndoTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/undo/BasicTextField2UndoTest.kt
new file mode 100644
index 0000000..bf37659
--- /dev/null
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text2/input/internal/undo/BasicTextField2UndoTest.kt
@@ -0,0 +1,353 @@
+/*
+ * 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.undo
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text2.BasicTextField2
+import androidx.compose.foundation.text2.input.TextFieldState
+import androidx.compose.foundation.text2.input.internal.selection.FakeClipboardManager
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.platform.LocalClipboardManager
+import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.assertTextEquals
+import androidx.compose.ui.test.hasSetTextAction
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.performSemanticsAction
+import androidx.compose.ui.test.performTextClearance
+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.text.TextRange
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalFoundationApi::class, ExperimentalTestApi::class)
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+internal class BasicTextField2UndoTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun canUndo_imeInsert() {
+        val state = TextFieldState("Hello", TextRange(5))
+
+        rule.setContent {
+            BasicTextField2(state)
+        }
+
+        rule.onNode(hasSetTextAction()).performTextInput(", World")
+        assertThat(state.text.toString()).isEqualTo("Hello, World")
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("Hello")
+        rule.onNode(hasSetTextAction()).assertTextEquals("Hello")
+    }
+
+    @Test
+    fun canRedo_imeInsert() {
+        val state = TextFieldState("Hello", TextRange(5))
+
+        rule.setContent {
+            BasicTextField2(state)
+        }
+
+        rule.onNode(hasSetTextAction()).performTextInput(", World")
+
+        state.undoState.undo()
+        rule.onNode(hasSetTextAction()).assertTextEquals("Hello")
+
+        state.undoState.redo()
+        rule.onNode(hasSetTextAction()).assertTextEquals("Hello, World")
+    }
+
+    @Test
+    fun undoMerges_imeInserts() {
+        val state = TextFieldState("Hello", TextRange(5))
+
+        rule.setContent {
+            BasicTextField2(state)
+        }
+
+        rule.onNode(hasSetTextAction()).typeText(", World")
+        assertThat(state.text.toString()).isEqualTo("Hello, World")
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("Hello")
+        rule.onNode(hasSetTextAction()).assertTextEquals("Hello")
+    }
+
+    @Test
+    fun undoMerges_imeInserts_onlyInForwardsDirection() {
+        val state = TextFieldState("Hello", TextRange(5))
+
+        rule.setContent {
+            BasicTextField2(state)
+        }
+
+        with(rule.onNode(hasSetTextAction())) {
+            performTextInput(", World")
+            performTextInputSelection(TextRange(5))
+            performTextInput(" Compose")
+        }
+        assertThat(state.text.toString()).isEqualTo("Hello Compose, World")
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("Hello, World")
+        rule.onNode(hasSetTextAction()).assertTextEquals("Hello, World")
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("Hello")
+        rule.onNode(hasSetTextAction()).assertTextEquals("Hello")
+    }
+
+    @Test
+    fun undoMerges_deletes() {
+        val state = TextFieldState("Hello, World", TextRange(12))
+
+        rule.setContent {
+            BasicTextField2(state)
+        }
+
+        with(rule.onNode(hasSetTextAction())) {
+            requestFocus()
+            performKeyInput {
+                repeat(12) {
+                    pressKey(Key.Backspace)
+                }
+            }
+        }
+        state.assertTextAndSelection("", TextRange.Zero)
+
+        state.undoState.undo()
+
+        state.assertTextAndSelection("Hello, World", TextRange(12))
+    }
+
+    @Test
+    fun undoDoesNotMerge_deletes_inBothDirections() {
+        val state = TextFieldState("Hello, World", TextRange(6))
+
+        rule.setContent {
+            BasicTextField2(state)
+        }
+
+        with(rule.onNode(hasSetTextAction())) {
+            requestFocus()
+            performKeyInput {
+                repeat(6) {
+                    pressKey(Key.Backspace)
+                }
+                repeat(6) {
+                    pressKey(Key.Delete)
+                }
+            }
+        }
+        state.assertTextAndSelection("", TextRange.Zero)
+
+        state.undoState.undo()
+        state.assertTextAndSelection(" World", TextRange(0))
+
+        state.undoState.undo()
+        state.assertTextAndSelection("Hello, World", TextRange(6))
+    }
+
+    @Test
+    fun undo_revertsSelection() {
+        val state = TextFieldState("Hello", TextRange(5))
+
+        rule.setContent {
+            BasicTextField2(state)
+        }
+
+        with(rule.onNode(hasSetTextAction())) {
+            performTextInputSelection(TextRange(0, 5))
+            performTextInput("a")
+        }
+        assertThat(state.text.toString()).isEqualTo("a")
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("Hello")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(0, 5))
+    }
+
+    @Test
+    fun redo_revertsSelection() {
+        val state = TextFieldState("Hello", TextRange(5))
+
+        rule.setContent {
+            BasicTextField2(state)
+        }
+
+        with(rule.onNode(hasSetTextAction())) {
+            performTextInputSelection(TextRange(2))
+            performTextInput(" abc ")
+        }
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(7))
+
+        state.undoState.undo()
+
+        assertThat(state.text.selectionInChars).isNotEqualTo(TextRange(7))
+
+        state.undoState.redo()
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(7))
+    }
+
+    @Test
+    fun variousEditOperations() {
+        val state = TextFieldState()
+
+        rule.setContent {
+            BasicTextField2(state)
+        }
+
+        with(rule.onNode(hasSetTextAction())) {
+            typeText("abc def")
+            performTextInputSelection(TextRange(4))
+            typeText("123 ")
+            performTextInputSelection(TextRange(0, 3))
+            typeText("ghi")
+            performTextClearance()
+        }
+        state.assertTextAndSelection("", TextRange.Zero)
+        state.undoState.undo()
+        state.assertTextAndSelection("ghi 123 def", TextRange(3))
+        state.undoState.undo()
+        state.assertTextAndSelection("g 123 def", TextRange(1))
+        state.undoState.undo()
+        state.assertTextAndSelection("abc 123 def", TextRange(0, 3))
+        state.undoState.undo()
+        state.assertTextAndSelection("abc def", TextRange(4))
+        state.undoState.undo()
+        state.assertTextAndSelection("", TextRange.Zero)
+        assertThat(state.undoState.canUndo).isFalse()
+    }
+
+    @Test
+    fun clearHistory_removesAllUndoAndRedo() {
+        val state = TextFieldState()
+
+        rule.setContent {
+            BasicTextField2(state)
+        }
+
+        with(rule.onNode(hasSetTextAction())) {
+            typeText("abc def")
+            performTextInputSelection(TextRange(4))
+            typeText("123 ")
+            performTextInputSelection(TextRange(0, 3))
+            typeText("ghi")
+            performTextClearance()
+        }
+        state.undoState.undo()
+        state.undoState.undo()
+        state.undoState.undo()
+
+        assertThat(state.undoState.canUndo).isTrue()
+        assertThat(state.undoState.canRedo).isTrue()
+
+        state.undoState.clearHistory()
+
+        assertThat(state.undoState.canUndo).isFalse()
+        assertThat(state.undoState.canRedo).isFalse()
+    }
+
+    @Test
+    fun paste_neverMerges() {
+        val state = TextFieldState()
+        val clipboardManager = FakeClipboardManager("ghi")
+
+        rule.setContent {
+            CompositionLocalProvider(LocalClipboardManager provides clipboardManager) {
+                BasicTextField2(state)
+            }
+        }
+
+        with(rule.onNode(hasSetTextAction())) {
+            typeText("abc def ")
+            performSemanticsAction(SemanticsActions.PasteText)
+            typeText(" jkl")
+        }
+        state.undoState.undo()
+
+        state.assertTextAndSelection("abc def ghi", TextRange(11))
+
+        state.undoState.undo()
+
+        state.assertTextAndSelection("abc def ", TextRange(8))
+
+        state.undoState.undo()
+
+        state.assertTextAndSelection("", TextRange.Zero)
+    }
+
+    @Test
+    fun cut_neverMerges() {
+        val state = TextFieldState("abc def ghi", TextRange(11))
+
+        rule.setContent {
+            BasicTextField2(state)
+        }
+
+        with(rule.onNode(hasSetTextAction())) {
+            requestFocus()
+            repeat(4) {
+                performKeyInput {
+                    pressKey(Key.Backspace)
+                }
+            }
+            performTextInputSelection(TextRange(4, 7))
+            performSemanticsAction(SemanticsActions.CutText)
+            repeat(4) {
+                performKeyInput {
+                    pressKey(Key.Backspace)
+                }
+            }
+        }
+        state.assertTextAndSelection("", TextRange.Zero)
+
+        state.undoState.undo()
+
+        state.assertTextAndSelection("abc ", TextRange(4))
+
+        state.undoState.undo()
+
+        state.assertTextAndSelection("abc def", TextRange(4, 7))
+
+        state.undoState.undo()
+
+        state.assertTextAndSelection("abc def ghi", TextRange(11))
+    }
+
+    private fun SemanticsNodeInteraction.typeText(text: String) {
+        text.forEach { performTextInput(it.toString()) }
+    }
+
+    private fun TextFieldState.assertTextAndSelection(text: String, selection: TextRange) {
+        assertThat(this.text.toString()).isEqualTo(text)
+        assertThat(this.text.selectionInChars).isEqualTo(selection)
+    }
+}
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 181cedc..58424db 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
@@ -90,7 +90,9 @@
                     )
                 }
 
-                if (!old.contentEquals(new)) {
+                // No need to restart the IME if keyboard type is configured as Password. IME
+                // should not keep an internal input state if the content needs to be secured.
+                if (!old.contentEquals(new) && imeOptions.keyboardType != KeyboardType.Password) {
                     composeImm.restartInput()
                 }
             }
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/selection/SelectionFakes.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/selection/SelectionFakes.kt
index 624fb70..df963b9 100644
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/selection/SelectionFakes.kt
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/selection/SelectionFakes.kt
@@ -274,6 +274,7 @@
     var endXHandleDirection = Direction.ON
     var endYHandleDirection = Direction.ON
     var rawPreviousHandleOffset = -1 // -1 = no previous offset
+    var layoutCoordinatesToReturn: LayoutCoordinates? = null
 
     private val selectableKey = 1L
     private val fakeSelectAllSelection: Selection = Selection(
@@ -313,7 +314,7 @@
     }
 
     override fun getLayoutCoordinates(): LayoutCoordinates? {
-        return null
+        return layoutCoordinatesToReturn
     }
 
     override fun getHandlePosition(selection: Selection, isStartHandle: Boolean): Offset {
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/selection/SelectionManagerTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/selection/SelectionManagerTest.kt
index b72ff38..295f7af 100644
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/selection/SelectionManagerTest.kt
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text/selection/SelectionManagerTest.kt
@@ -17,10 +17,8 @@
 package androidx.compose.foundation.text.selection
 
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.hapticfeedback.HapticFeedback
 import androidx.compose.ui.hapticfeedback.HapticFeedbackType
-import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.platform.ClipboardManager
 import androidx.compose.ui.platform.TextToolbar
 import androidx.compose.ui.text.AnnotatedString
@@ -31,8 +29,6 @@
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 import org.mockito.kotlin.any
-import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.eq
 import org.mockito.kotlin.isNull
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.never
@@ -49,9 +45,7 @@
     private val selectionManager = SelectionManager(selectionRegistrar)
     private var onSelectionChangeCalledTimes = 0
 
-    private val containerLayoutCoordinates = mock<LayoutCoordinates> {
-        on { isAttached } doReturn true
-    }
+    private val containerLayoutCoordinates = MockCoordinates()
 
     private val startSelectableId = 2L
     private val startSelectable = mock<Selectable> {
@@ -249,7 +243,7 @@
         val startOffset = text.indexOf('e')
         val endOffset = text.indexOf('m')
         selectable.textToReturn = annotatedString
-        selectionManager.selection = Selection(
+        val selection = Selection(
             start = Selection.AnchorInfo(
                 direction = ResolvedTextDirection.Ltr,
                 offset = startOffset,
@@ -262,6 +256,8 @@
             ),
             handlesCrossed = false
         )
+        selectionManager.selection = selection
+        selectionRegistrar.subselections = mapOf(selectableId to selection)
 
         assertThat(selectionManager.isNonEmptySelection()).isTrue()
     }
@@ -272,7 +268,7 @@
         val annotatedString = AnnotatedString(text)
         val startOffset = text.indexOf('e')
         selectable.textToReturn = annotatedString
-        selectionManager.selection = Selection(
+        val selection = Selection(
             start = Selection.AnchorInfo(
                 direction = ResolvedTextDirection.Ltr,
                 offset = startOffset,
@@ -285,6 +281,8 @@
             ),
             handlesCrossed = false
         )
+        selectionManager.selection = selection
+        selectionRegistrar.subselections = mapOf(selectableId to selection)
 
         assertThat(selectionManager.isNonEmptySelection()).isFalse()
     }
@@ -318,6 +316,48 @@
             handlesCrossed = true
         )
 
+        selectionRegistrar.subselections = mapOf(
+            endSelectableId to Selection(
+                start = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = annotatedString.length,
+                    selectableId = endSelectableId
+                ),
+                end = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = endOffset,
+                    selectableId = endSelectableId
+                ),
+                handlesCrossed = true
+            ),
+            middleSelectableId to Selection(
+                start = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = annotatedString.length,
+                    selectableId = middleSelectableId
+                ),
+                end = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = 0,
+                    selectableId = middleSelectableId
+                ),
+                handlesCrossed = true
+            ),
+            startSelectableId to Selection(
+                start = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = startOffset,
+                    selectableId = startSelectableId
+                ),
+                end = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = 0,
+                    selectableId = startSelectableId
+                ),
+                handlesCrossed = true
+            ),
+        )
+
         assertThat(selectionManager.isNonEmptySelection()).isTrue()
     }
 
@@ -350,6 +390,48 @@
             handlesCrossed = false
         )
 
+        selectionRegistrar.subselections = mapOf(
+            startSelectableId to Selection(
+                start = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = annotatedString.length,
+                    selectableId = startSelectableId
+                ),
+                end = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = annotatedString.length,
+                    selectableId = startSelectableId
+                ),
+                handlesCrossed = false
+            ),
+            middleSelectableId to Selection(
+                start = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = 0,
+                    selectableId = middleSelectableId
+                ),
+                end = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = 0,
+                    selectableId = middleSelectableId
+                ),
+                handlesCrossed = false
+            ),
+            endSelectableId to Selection(
+                start = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = 0,
+                    selectableId = endSelectableId
+                ),
+                end = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = 0,
+                    selectableId = endSelectableId
+                ),
+                handlesCrossed = false
+            ),
+        )
+
         assertThat(selectionManager.isNonEmptySelection()).isFalse()
     }
 
@@ -382,12 +464,55 @@
             handlesCrossed = true
         )
 
+        selectionRegistrar.subselections = mapOf(
+            startSelectableId to Selection(
+                start = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = annotatedString.length,
+                    selectableId = startSelectableId
+                ),
+                end = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = annotatedString.length,
+                    selectableId = startSelectableId
+                ),
+                handlesCrossed = true
+            ),
+            middleSelectableId to Selection(
+                start = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = 0,
+                    selectableId = middleSelectableId
+                ),
+                end = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = 0,
+                    selectableId = middleSelectableId
+                ),
+                handlesCrossed = true
+            ),
+            endSelectableId to Selection(
+                start = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = 0,
+                    selectableId = endSelectableId
+                ),
+                end = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = 0,
+                    selectableId = endSelectableId
+                ),
+                handlesCrossed = true
+            ),
+        )
+
         assertThat(selectionManager.isNonEmptySelection()).isFalse()
     }
 
     @Test
     fun getSelectedText_selection_null_return_null() {
         selectionManager.selection = null
+        selectionRegistrar.subselections = emptyMap()
 
         assertThat(selectionManager.getSelectedText()).isNull()
         assertThat(selectable.getTextCalledTimes).isEqualTo(0)
@@ -400,7 +525,7 @@
         val startOffset = text.indexOf('e')
         val endOffset = text.indexOf('m')
         selectable.textToReturn = annotatedString
-        selectionManager.selection = Selection(
+        val selection = Selection(
             start = Selection.AnchorInfo(
                 direction = ResolvedTextDirection.Ltr,
                 offset = startOffset,
@@ -413,6 +538,8 @@
             ),
             handlesCrossed = false
         )
+        selectionManager.selection = selection
+        selectionRegistrar.subselections = mapOf(selectableId to selection)
 
         assertThat(selectionManager.getSelectedText())
             .isEqualTo(annotatedString.subSequence(startOffset, endOffset))
@@ -426,7 +553,7 @@
         val startOffset = text.indexOf('m')
         val endOffset = text.indexOf('x')
         selectable.textToReturn = annotatedString
-        selectionManager.selection = Selection(
+        val selection = Selection(
             start = Selection.AnchorInfo(
                 direction = ResolvedTextDirection.Ltr,
                 offset = startOffset,
@@ -439,6 +566,8 @@
             ),
             handlesCrossed = true
         )
+        selectionManager.selection = selection
+        selectionRegistrar.subselections = mapOf(selectableId to selection)
 
         assertThat(selectionManager.getSelectedText())
             .isEqualTo(annotatedString.subSequence(endOffset, startOffset))
@@ -474,6 +603,48 @@
             handlesCrossed = false
         )
 
+        selectionRegistrar.subselections = mapOf(
+            startSelectableId to Selection(
+                start = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = startOffset,
+                    selectableId = startSelectableId
+                ),
+                end = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = annotatedString.length,
+                    selectableId = startSelectableId
+                ),
+                handlesCrossed = false
+            ),
+            middleSelectableId to Selection(
+                start = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = 0,
+                    selectableId = middleSelectableId
+                ),
+                end = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = annotatedString.length,
+                    selectableId = middleSelectableId
+                ),
+                handlesCrossed = false
+            ),
+            endSelectableId to Selection(
+                start = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = 0,
+                    selectableId = endSelectableId
+                ),
+                end = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = endOffset,
+                    selectableId = endSelectableId
+                ),
+                handlesCrossed = false
+            ),
+        )
+
         val result = annotatedString.subSequence(startOffset, annotatedString.length) +
             annotatedString + annotatedString.subSequence(0, endOffset)
         assertThat(selectionManager.getSelectedText()).isEqualTo(result)
@@ -513,6 +684,48 @@
             handlesCrossed = true
         )
 
+        selectionRegistrar.subselections = mapOf(
+            endSelectableId to Selection(
+                start = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = annotatedString.length,
+                    selectableId = endSelectableId
+                ),
+                end = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = endOffset,
+                    selectableId = endSelectableId
+                ),
+                handlesCrossed = true
+            ),
+            middleSelectableId to Selection(
+                start = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = annotatedString.length,
+                    selectableId = middleSelectableId
+                ),
+                end = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = 0,
+                    selectableId = middleSelectableId
+                ),
+                handlesCrossed = true
+            ),
+            startSelectableId to Selection(
+                start = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = startOffset,
+                    selectableId = startSelectableId
+                ),
+                end = Selection.AnchorInfo(
+                    direction = ResolvedTextDirection.Ltr,
+                    offset = 0,
+                    selectableId = startSelectableId
+                ),
+                handlesCrossed = true
+            ),
+        )
+
         val result = annotatedString.subSequence(endOffset, annotatedString.length) +
             annotatedString + annotatedString.subSequence(0, startOffset)
         assertThat(selectionManager.getSelectedText()).isEqualTo(result)
@@ -526,6 +739,7 @@
     @Test
     fun copy_selection_null_not_trigger_clipboardManager() {
         selectionManager.selection = null
+        selectionRegistrar.subselections = emptyMap()
 
         selectionManager.copy()
 
@@ -539,7 +753,7 @@
         val startOffset = text.indexOf('m')
         val endOffset = text.indexOf('x')
         selectable.textToReturn = annotatedString
-        selectionManager.selection = Selection(
+        val selection = Selection(
             start = Selection.AnchorInfo(
                 direction = ResolvedTextDirection.Ltr,
                 offset = startOffset,
@@ -552,6 +766,8 @@
             ),
             handlesCrossed = true
         )
+        selectionManager.selection = selection
+        selectionRegistrar.subselections = mapOf(selectableId to selection)
 
         selectionManager.copy()
 
@@ -570,7 +786,8 @@
         val startOffset = text.indexOf('m')
         val endOffset = text.indexOf('x')
         selectable.textToReturn = annotatedString
-        selectionManager.selection = Selection(
+        selectable.layoutCoordinatesToReturn = containerLayoutCoordinates
+        val selection = Selection(
             start = Selection.AnchorInfo(
                 direction = ResolvedTextDirection.Ltr,
                 offset = startOffset,
@@ -583,12 +800,14 @@
             ),
             handlesCrossed = true
         )
+        selectionManager.selection = selection
+        selectionRegistrar.subselections = mapOf(selectableId to selection)
         selectionManager.hasFocus = true
 
         selectionManager.showToolbar = true
 
         verify(textToolbar, times(1)).showMenu(
-            eq(Rect.Zero),
+            any(),
             any(),
             isNull(),
             isNull(),
@@ -603,7 +822,7 @@
         val startOffset = text.indexOf('m')
         val endOffset = text.indexOf('x')
         selectable.textToReturn = annotatedString
-        selectionManager.selection = Selection(
+        val selection = Selection(
             start = Selection.AnchorInfo(
                 direction = ResolvedTextDirection.Ltr,
                 offset = startOffset,
@@ -616,12 +835,14 @@
             ),
             handlesCrossed = true
         )
+        selectionManager.selection = selection
+        selectionRegistrar.subselections = mapOf(selectableId to selection)
         selectionManager.hasFocus = false
 
         selectionManager.showToolbar = true
 
         verify(textToolbar, never()).showMenu(
-            eq(Rect.Zero),
+            any(),
             any(),
             isNull(),
             isNull(),
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/AllCapsTransformationTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/AllCapsTransformationTest.kt
new file mode 100644
index 0000000..1f20b17
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/AllCapsTransformationTest.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.compose.foundation.text2.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.ui.text.input.KeyboardCapitalization
+import androidx.compose.ui.text.intl.Locale
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalFoundationApi::class)
+@RunWith(JUnit4::class)
+class AllCapsTransformationTest {
+
+    @Test
+    fun allCapsTransformation_definesCharacterCapitalizationKeyboardOption() {
+        val transformation = InputTransformation.allCaps(Locale.current)
+        assertThat(transformation.keyboardOptions?.capitalization)
+            .isEqualTo(KeyboardCapitalization.Characters)
+    }
+
+    @Test
+    fun allNewTypedCharacters_convertedToUppercase() {
+        val transformation = InputTransformation.allCaps(Locale("en_US"))
+
+        val originalValue = TextFieldCharSequence("")
+        val buffer = TextFieldBuffer(originalValue).apply {
+            append("hello")
+        }
+
+        transformation.transformInput(originalValue, buffer)
+
+        assertThat(buffer.toString()).isEqualTo("HELLO")
+    }
+
+    @Test
+    fun oldCharacters_areNotConverted() {
+        val transformation = InputTransformation.allCaps(Locale("en_US"))
+
+        val originalValue = TextFieldCharSequence("hello")
+        val buffer = TextFieldBuffer(originalValue).apply {
+            append(" world")
+        }
+
+        transformation.transformInput(originalValue, buffer)
+
+        assertThat(buffer.toString()).isEqualTo("hello WORLD")
+    }
+
+    @Test
+    fun localeDifference_turkishI() {
+        val transformation = InputTransformation.allCaps(Locale("tr"))
+
+        val originalValue = TextFieldCharSequence("")
+        val buffer = TextFieldBuffer(originalValue).apply {
+            append("i")
+        }
+
+        transformation.transformInput(originalValue, buffer)
+
+        assertThat(buffer.toString()).isEqualTo("\u0130") // Turkish dotted capital i
+    }
+
+    @Test
+    fun multipleEdits() {
+        val transformation = InputTransformation.allCaps(Locale("en_US"))
+
+        var originalValue = TextFieldCharSequence("hello")
+        var buffer = TextFieldBuffer(originalValue)
+
+        with(buffer) {
+            delete(0, 3) // lo
+            replace(1, 1, "abc") // lABCo
+        }
+
+        transformation.transformInput(originalValue, buffer)
+
+        originalValue = buffer.toTextFieldCharSequence()
+        buffer = TextFieldBuffer(originalValue)
+
+        with(buffer) {
+            delete(2, 3) // lACo
+            append("xyz") // lACoXYZ
+        }
+
+        transformation.transformInput(originalValue, buffer)
+
+        assertThat(buffer.toString()).isEqualTo("lACoXYZ")
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/TextFieldStateSaverTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/TextFieldStateSaverTest.kt
index 308918e..ef655cf 100644
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/TextFieldStateSaverTest.kt
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/TextFieldStateSaverTest.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation.text2.input
 
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text2.input.internal.commitText
 import androidx.compose.runtime.saveable.SaverScope
 import androidx.compose.ui.text.TextRange
 import com.google.common.truth.Truth.assertThat
@@ -39,6 +40,26 @@
         assertThat(restoredState.text.selectionInChars).isEqualTo(TextRange(0, 5))
     }
 
+    @Test
+    fun savesAndRestoresUndo() {
+        val state = TextFieldState("hello, world", initialSelectionInChars = TextRange(0, 5))
+
+        state.editAsUser(null) {
+            commitText("hi", 1)
+        }
+
+        val saved = with(TextFieldState.Saver) { TestSaverScope.save(state) }
+        assertNotNull(saved)
+        val restoredState = TextFieldState.Saver.restore(saved)
+
+        assertNotNull(restoredState)
+        assertThat(restoredState.text.toString()).isEqualTo("hi, world")
+        assertThat(restoredState.undoState.canUndo).isTrue()
+        restoredState.undoState.undo()
+        assertThat(restoredState.text.toString()).isEqualTo("hello, world")
+        assertThat(restoredState.text.selectionInChars).isEqualTo(TextRange(0, 5))
+    }
+
     private object TestSaverScope : SaverScope {
         override fun canBeSaved(value: Any): Boolean = true
     }
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTrackerTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTrackerTest.kt
index 3029a8e..5de9db6 100644
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTrackerTest.kt
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/ChangeTrackerTest.kt
@@ -92,6 +92,18 @@
     }
 
     @Test
+    fun replaceWithReversedIndices() {
+        val buffer = SimpleBuffer("abcd")
+
+        buffer.replace(2, 0, "e")
+
+        assertThat(buffer.toString()).isEqualTo("ecd")
+        assertThat(buffer.changes.changeCount).isEqualTo(1)
+        assertThat(buffer.changes.getRange(0)).isEqualTo(TextRange(0, 1))
+        assertThat(buffer.changes.getOriginalRange(0)).isEqualTo(TextRange(0, 2))
+    }
+
+    @Test
     fun multipleAdjacentReplaces_whenPerformedInOrder_replacementsShorter() {
         val buffer = SimpleBuffer("abcd")
 
@@ -203,6 +215,11 @@
             }
         }
 
+        fun replace(start: Int, end: Int, text: String) {
+            changes.trackChange(start, end, text.length)
+            builder.replace(minOf(start, end), maxOf(start, end), text)
+        }
+
         override fun toString(): String = builder.toString()
     }
 }
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/EditingBufferTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/EditingBufferTest.kt
index 99de440..6dca492 100644
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/EditingBufferTest.kt
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/EditingBufferTest.kt
@@ -163,6 +163,17 @@
     }
 
     @Test
+    fun replace_reversedRegion() {
+        val eb = EditingBuffer("ABCDE", TextRange.Zero)
+        eb.replace(3, 1, "FGHI")
+
+        assertThat(eb).hasChars("AFGHIDE")
+        assertThat(eb.cursor).isEqualTo(5)
+        assertThat(eb.selectionStart).isEqualTo(5)
+        assertThat(eb.selectionEnd).isEqualTo(5)
+    }
+
+    @Test
     fun setComposition_and_cancelComposition() {
         val eb = EditingBuffer("ABCDE", TextRange.Zero)
 
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldStateInternalBufferTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldStateInternalBufferTest.kt
index 63242e6..656b1d3 100644
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldStateInternalBufferTest.kt
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/TextFieldStateInternalBufferTest.kt
@@ -513,6 +513,6 @@
     ) = TextFieldState(value.toString(), value.selectionInChars)
 
     private fun TextFieldState.editAsUser(block: EditingBuffer.() -> Unit) {
-        editAsUser(null, false, block)
+        editAsUser(inputTransformation = null, notifyImeOfChanges = false, block = block)
     }
 }
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/undo/TextUndoTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/undo/TextUndoTest.kt
new file mode 100644
index 0000000..67d75d4
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/undo/TextUndoTest.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.undo
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text2.input.InputTransformation
+import androidx.compose.foundation.text2.input.TextFieldState
+import androidx.compose.foundation.text2.input.allCaps
+import androidx.compose.foundation.text2.input.internal.commitText
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.intl.Locale
+import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalFoundationApi::class)
+@RunWith(JUnit4::class)
+class TextUndoTest {
+
+    @Test
+    fun insertionFromEndPointCanMerge() {
+        val state = TextFieldState()
+        state.typeAtEnd("a")
+        state.typeAtEnd("b")
+        state.typeAtEnd("c")
+
+        assertThat(state.text.toString()).isEqualTo("abc")
+        assertThat(state.undoState.canUndo).isTrue()
+
+        state.undoState.undo()
+
+        assertThat(state.text.toString()).isEqualTo("")
+    }
+
+    @Test
+    fun insertionFromStartPointCannotMerge() {
+        val state = TextFieldState()
+        state.typeAtStart("a")
+        state.typeAtStart("b")
+        state.typeAtStart("c")
+
+        assertThat(state.text.toString()).isEqualTo("cba")
+        assertThat(state.undoState.canUndo).isTrue()
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("ba")
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("a")
+    }
+
+    @Test
+    fun insertionFromMiddleCannotMerge() {
+        val state = TextFieldState()
+        state.typeAtEnd("a")
+        state.typeAtEnd("c")
+        state.typeAt(1, "b")
+
+        assertThat(state.text.toString()).isEqualTo("abc")
+        assertThat(state.undoState.canUndo).isTrue()
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("ac")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("")
+    }
+
+    @Test
+    fun deletionFromEndPointCanMerge() {
+        val state = TextFieldState("abc")
+        state.placeCursorAt(3)
+        state.deleteAt(2)
+        state.deleteAt(1)
+        state.deleteAt(0)
+
+        assertThat(state.text.toString()).isEqualTo("")
+        assertThat(state.undoState.canUndo).isTrue()
+
+        state.undoState.undo()
+
+        assertThat(state.text.toString()).isEqualTo("abc")
+    }
+
+    @Test
+    fun deletionFromStartPointCanMerge() {
+        val state = TextFieldState("abc")
+        state.placeCursorAt(0)
+        state.deleteAt(0)
+        state.deleteAt(0)
+        state.deleteAt(0)
+
+        assertThat(state.text.toString()).isEqualTo("")
+        assertThat(state.undoState.canUndo).isTrue()
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("abc")
+    }
+
+    @Test
+    fun deletionFromMiddleCannotMerge() {
+        val state = TextFieldState("abc")
+        state.placeCursorAt(2) // "ab|c"
+        state.deleteAt(1) // "a|c"
+        state.deleteAt(1) // "a|"
+        state.deleteAt(0) // "|"
+
+        assertThat(state.text.toString()).isEqualTo("")
+        assertThat(state.undoState.canUndo).isTrue()
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("a")
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("ac")
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("abc")
+    }
+
+    @Test
+    fun deletionsWithDifferentDirectionsCannotMerge() {
+        val state = TextFieldState("abcdef")
+        state.placeCursorAt(3) // "abc|def"
+        state.deleteAt(2) // "ab|def"
+        state.deleteAt(2) // "ab|ef"
+
+        assertThat(state.text.toString()).isEqualTo("abef")
+        assertThat(state.undoState.canUndo).isTrue()
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("abdef")
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("abcdef")
+    }
+
+    @Test
+    fun insertionAndDeletionNeverMerge() {
+        val state = TextFieldState()
+        state.typeAtEnd("a") // "a|"
+        state.typeAtEnd("b") // "ab|"
+        state.deleteAt(1) // "a|"
+        state.typeAtStart("c") // "|a" -> "c|a"
+
+        assertThat(state.text.toString()).isEqualTo("ca")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("a")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(0))
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("ab")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("")
+    }
+
+    @Test
+    fun replaceDoesNotMergeWithInsertion() {
+        val state = TextFieldState("abc")
+        state.typeAtEnd("d") // "abcd|"
+        state.replaceAt(0, 4, "def") // "def|"
+        state.typeAtEnd("g") // "defg|"
+
+        assertThat(state.text.toString()).isEqualTo("defg")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4))
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("def")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(3))
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("abcd")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4))
+    }
+
+    @Test
+    fun replaceDoesNotMergeWithDeletion() {
+        val state = TextFieldState("abc")
+        state.placeCursorAt(3)
+        state.deleteAt(2) // "ab|"
+        state.replaceAt(0, 2, "def") // "def|"
+        state.deleteAt(2) // "de|"
+
+        assertThat(state.text.toString()).isEqualTo("de")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("def")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(3))
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("ab")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
+    }
+
+    @Test
+    fun newLineInsert_doesNotMerge() {
+        val state = TextFieldState()
+        state.typeAtEnd("a") // "a|"
+        state.typeAtEnd("b") // "ab|"
+        state.typeAtEnd("\n") // "ab\n|"
+        state.typeAtEnd("c") // "ab\nc|"
+
+        assertThat(state.text.toString()).isEqualTo("ab\nc")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4))
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("ab\n")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(3))
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("ab")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2))
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("")
+    }
+
+    @Ignore("Enable after EditingBuffer reverse range replace is fixed")
+    @Test
+    fun undoRecoversSelectionState() {
+        val state = TextFieldState("abc")
+        state.select(2, 0) // "|ab|c"
+        state.type("d") // "d|c"
+
+        assertThat(state.text.toString()).isEqualTo("dc")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
+
+        state.undoState.undo()
+        assertThat(state.text.toString()).isEqualTo("abc")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(2, 0))
+    }
+
+    @Ignore("Enable after EditingBuffer reverse range replace is fixed")
+    @Test
+    fun redoDoesNotRecoverSelectionState() {
+        val state = TextFieldState("abc")
+        state.typeAtEnd("d") // "abcd|"
+        state.select(2, 0) // "|ab|cd"
+        state.type("e") // "e|cd"
+
+        assertThat(state.text.toString()).isEqualTo("ecd")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(1))
+
+        state.undoState.undo() // "|ab|cd"
+        state.undoState.undo() // "|abc"
+        state.undoState.redo() // "abcd|"
+
+        assertThat(state.text.toString()).isEqualTo("abcd")
+        assertThat(state.text.selectionInChars).isEqualTo(TextRange(4, 4))
+    }
+
+    @Ignore("Enable after allCapsTransformation is fixed")
+    @Test
+    fun undoHistoryIncludesInputTransformation() {
+        val allCapsTransformation = InputTransformation.allCaps(Locale.current)
+        val state = TextFieldState("abc", TextRange(3))
+
+        // this test also tests for AllCapsTransformation
+        state.editAsUser(inputTransformation = allCapsTransformation) {
+            commitComposition()
+            commitText("d", 1)
+        } // "abcD|"
+        state.editAsUser(inputTransformation = allCapsTransformation) {
+            commitComposition()
+            commitText("e", 1)
+        } // "abcDE|"
+
+        state.undoState.undo() // "abc|"
+        assertThat(state.text.toString()).isEqualTo("abc")
+
+        state.undoState.redo() // "abcDE|"
+        assertThat(state.text.toString()).isEqualTo("abcDE")
+    }
+
+    @Test
+    fun directEditsClearTheUndoHistory() {
+        val state = TextFieldState("abc")
+        state.typeAtEnd("d")
+        state.typeAtStart("e")
+        state.typeAtEnd("f")
+
+        state.edit { replace(0, 1, "x") }
+
+        assertThat(state.undoState.canUndo).isEqualTo(false)
+        assertThat(state.undoState.canRedo).isEqualTo(false)
+    }
+
+    companion object {
+
+        private fun TextFieldState.typeAtEnd(text: String) {
+            placeCursorAt(this.text.length)
+            typeAt(this.text.length, text)
+        }
+
+        private fun TextFieldState.typeAtStart(text: String) {
+            placeCursorAt(0)
+            typeAt(0, text)
+        }
+
+        private fun TextFieldState.typeAt(index: Int, text: String) {
+            placeCursorAt(index)
+            editAsUser(inputTransformation = null) {
+                replace(index, index, text)
+            }
+        }
+
+        private fun TextFieldState.type(text: String) {
+            editAsUser(inputTransformation = null) {
+                commitComposition()
+                commitText(text, 1)
+            }
+        }
+
+        private fun TextFieldState.deleteAt(index: Int) {
+            editAsUser(inputTransformation = null) {
+                delete(index, index + 1)
+            }
+        }
+
+        private fun TextFieldState.placeCursorAt(index: Int) {
+            select(index, index)
+        }
+
+        private fun TextFieldState.select(start: Int, end: Int) {
+            editAsUser(inputTransformation = null) {
+                setSelection(start, end)
+            }
+        }
+
+        private fun TextFieldState.replaceAt(start: Int, end: Int, newText: String) {
+            editAsUser(inputTransformation = null) {
+                replace(start, end, newText)
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/undo/UndoManagerSaverTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/undo/UndoManagerSaverTest.kt
new file mode 100644
index 0000000..750fbe6
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/undo/UndoManagerSaverTest.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.undo
+
+import androidx.compose.runtime.saveable.SaverScope
+import androidx.compose.runtime.saveable.autoSaver
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertNotNull
+import org.junit.Test
+
+class UndoManagerSaverTest {
+
+    @Test
+    fun savesAndRestoresTextAndSelection() {
+        val undoManager = UndoManager<Int>()
+
+        undoManager.record(1)
+        undoManager.record(2)
+        undoManager.record(3)
+
+        undoManager.undo()
+
+        // undoStack; 1-2 redoStack; 3
+
+        val saver = UndoManager.createSaver(autoSaver<Int>())
+        val saved = with(saver) {
+            TestSaverScope.save(undoManager)
+        }
+        assertNotNull(saved)
+        val restoredState = saver.restore(saved)
+
+        assertNotNull(restoredState)
+        assertThat(restoredState.canUndo).isTrue()
+        assertThat(restoredState.canRedo).isTrue()
+
+        var redoValue = undoManager.redo()
+
+        assertThat(redoValue).isEqualTo(3)
+
+        val undoValues = mutableListOf<Int>()
+        while (undoManager.canUndo) {
+            undoValues += undoManager.undo()
+        }
+
+        assertThat(undoValues).containsExactly(3, 2, 1)
+    }
+
+    private object TestSaverScope : SaverScope {
+        override fun canBeSaved(value: Any): Boolean = true
+    }
+}
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/undo/UndoManagerTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/undo/UndoManagerTest.kt
new file mode 100644
index 0000000..cbae21e
--- /dev/null
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/text2/input/internal/undo/UndoManagerTest.kt
@@ -0,0 +1,190 @@
+/*
+ * 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.undo
+
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class UndoManagerTest {
+
+    @Test
+    fun negativeCapacityThrows() {
+        assertFailsWith<IllegalArgumentException>("Capacity must be a positive integer") {
+            UndoManager<Int>(capacity = -1)
+        }
+    }
+
+    @Test
+    fun initialLowCapacityThrows_undoStack() {
+        assertFailsWith<IllegalArgumentException>(
+            getInitialCapacityErrorMessage(
+                capacity = 1,
+                totalStackSize = 2
+            )
+        ) {
+            UndoManager(
+                initialUndoStack = listOf(1, 2),
+                capacity = 1
+            )
+        }
+    }
+
+    @Test
+    fun initialLowCapacityThrows_redoStack() {
+        assertFailsWith<IllegalArgumentException>(
+            getInitialCapacityErrorMessage(
+                capacity = 1,
+                totalStackSize = 2
+            )
+        ) {
+            UndoManager(
+                initialRedoStack = listOf(1, 2),
+                capacity = 1
+            )
+        }
+    }
+
+    @Test
+    fun initialLowCapacityThrows_bothStacks() {
+        assertFailsWith<IllegalArgumentException>(
+            getInitialCapacityErrorMessage(
+                capacity = 3,
+                totalStackSize = 4
+            )
+        ) {
+            UndoManager(
+                initialUndoStack = listOf(1, 2),
+                initialRedoStack = listOf(1, 2),
+                capacity = 3
+            )
+        }
+    }
+
+    @Test
+    fun commitSingleItem_canUndo() {
+        val undoManager = UndoManager<Int>()
+        undoManager.record(1)
+
+        assertThat(undoManager.canUndo).isTrue()
+        assertThat(undoManager.canRedo).isFalse()
+
+        undoManager.undo()
+        assertThat(undoManager.canUndo).isFalse()
+        assertThat(undoManager.canRedo).isTrue()
+    }
+
+    @Test
+    fun cannotRedoWithoutFirstUndo() {
+        val undoManager = UndoManager<Int>()
+        undoManager.record(1)
+        undoManager.record(2)
+        undoManager.record(3)
+
+        assertThat(undoManager.canRedo).isFalse()
+        assertFailsWith<IllegalStateException>(
+            "It's an error to call redo while there is nothing to redo. " +
+                "Please first check `canRedo` value before calling the `redo` function."
+        ) {
+            undoManager.redo()
+        }
+    }
+
+    @Test
+    fun commitItem_clearsRedoStack() {
+        val undoManager = UndoManager<Int>()
+        undoManager.record(1)
+        undoManager.record(2)
+        undoManager.record(3)
+
+        undoManager.undo()
+        assertThat(undoManager.canRedo).isTrue()
+
+        undoManager.record(4)
+        assertThat(undoManager.canRedo).isFalse()
+    }
+
+    @Test
+    fun clearHistoryRemovesUndoAndRedo() {
+        val undoManager = UndoManager<Int>()
+        undoManager.record(1)
+        undoManager.record(2)
+        undoManager.record(3)
+
+        undoManager.undo()
+
+        assertThat(undoManager.canUndo).isTrue()
+        assertThat(undoManager.canRedo).isTrue()
+
+        undoManager.clearHistory()
+
+        assertThat(undoManager.canUndo).isFalse()
+        assertThat(undoManager.canRedo).isFalse()
+    }
+
+    @Test
+    fun capacityOverflow_removesFromTheBottomOfStack() {
+        val undoManager = UndoManager<Int>(capacity = 2)
+        undoManager.record(1)
+        undoManager.record(2)
+        // overflow the capacity, undo history should forget the first item
+        undoManager.record(3)
+
+        var item = undoManager.undo()
+        assertThat(item).isEqualTo(3)
+
+        item = undoManager.undo()
+        assertThat(item).isEqualTo(2)
+
+        assertThat(undoManager.canUndo).isFalse()
+    }
+
+    @Test
+    fun capacityOverflow_shouldRemoveRedoActionsFirst() {
+        val undoManager = UndoManager<Int>(capacity = 20)
+        undoManager.record(1)
+        undoManager.record(2)
+        undoManager.record(3)
+        undoManager.record(4)
+
+        undoManager.undo() // total size does not change  undo; 1-2-3 redo; 4
+        undoManager.undo() // total size does not change  undo; 1-2 redo; 4-3
+
+        // this should not remove anything from the undo stack, auto removed items from redo should
+        // suffice
+        undoManager.record(5)
+
+        assertThat(undoManager.canUndo).isTrue()
+        assertThat(undoManager.canRedo).isFalse()
+
+        var item = undoManager.undo()
+        assertThat(item).isEqualTo(5)
+
+        item = undoManager.undo()
+        assertThat(item).isEqualTo(2)
+
+        item = undoManager.undo()
+        assertThat(item).isEqualTo(1)
+    }
+
+    private fun getInitialCapacityErrorMessage(capacity: Int, totalStackSize: Int) =
+        "Initial list of undo and redo operations have a size=($totalStackSize) greater " +
+        "than the given capacity=($capacity)."
+}
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 dd0593b..99b38b0 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
@@ -20,8 +20,7 @@
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.saveable.Saver
-import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.window.PopupPositionProvider
@@ -79,10 +78,9 @@
     isPersistent: Boolean = true,
     mutatorMutex: MutatorMutex = BasicTooltipDefaults.GlobalMutatorMutex
 ): BasicTooltipState =
-    rememberSaveable(
+    remember(
         isPersistent,
-        mutatorMutex,
-        saver = BasicTooltipStateImpl.Saver
+        mutatorMutex
     ) {
         BasicTooltipStateImpl(
             initialIsVisible = initialIsVisible,
@@ -180,29 +178,6 @@
     override fun onDispose() {
         job?.cancel()
     }
-
-    companion object {
-        /**
-         * The default [Saver] implementation for [BasicTooltipStateImpl].
-         */
-        val Saver = Saver<BasicTooltipStateImpl, Any>(
-            save = {
-                   listOf(
-                       it.isVisible,
-                       it.isPersistent,
-                       it.mutatorMutex
-                   )
-            },
-            restore = {
-                val (isVisible, isPersistent, mutatorMutex) = it as List<*>
-                BasicTooltipStateImpl(
-                    initialIsVisible = isVisible as Boolean,
-                    isPersistent = isPersistent as Boolean,
-                    mutatorMutex = mutatorMutex as MutatorMutex,
-                )
-            }
-        )
-    }
 }
 
 /**
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt
index 4b6c4856..08efb0a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt
@@ -303,15 +303,18 @@
     private val _canDrag: (PointerInputChange) -> Boolean = { canDrag(it) }
     private val _startDragImmediately: () -> Boolean = { startDragImmediately() }
     private val velocityTracker = VelocityTracker()
+    private var isListeningForEvents = false
 
-    /**
-     * To preserve the original behavior we had (before the Modifier.Node migration) we need to
-     * scope the DragStopped and DragCancel methods to the node's coroutine scope instead of using
-     * the one provided by the pointer input modifier, this is to ensure that even when the pointer
-     * input scope is reset we will continue any coroutine scope scope that we started from these
-     * methods while the pointer input scope was active.
-     */
-    override fun onAttach() {
+    private fun startListeningForEvents() {
+        isListeningForEvents = true
+
+        /**
+         * To preserve the original behavior we had (before the Modifier.Node migration) we need to
+         * scope the DragStopped and DragCancel methods to the node's coroutine scope instead of using
+         * the one provided by the pointer input modifier, this is to ensure that even when the pointer
+         * input scope is reset we will continue any coroutine scope scope that we started from these
+         * methods while the pointer input scope was active.
+         */
         coroutineScope.launch {
             while (isActive) {
                 var event = channel.receive()
@@ -349,6 +352,13 @@
                             velocityTracker,
                             orientation
                         )?.let {
+                            /**
+                             * The gesture crossed the touch slop, events are now relevant
+                             * and should be propagated
+                             */
+                            if (!isListeningForEvents) {
+                                startListeningForEvents()
+                            }
                             var isDragSuccessful = false
                             try {
                                 isDragSuccessful = awaitDrag(
@@ -391,6 +401,7 @@
     private var dragInteraction: DragInteraction.Start? = null
 
     override fun onDetach() {
+        isListeningForEvents = false
         disposeInteractionSource()
     }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListAnimateScrollScope.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListAnimateScrollScope.kt
index 7df5322..7122368 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListAnimateScrollScope.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListAnimateScrollScope.kt
@@ -16,12 +16,14 @@
 
 package androidx.compose.foundation.lazy
 
+import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.gestures.ScrollScope
 import androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope
 import androidx.compose.ui.util.fastFirstOrNull
 import androidx.compose.ui.util.fastSumBy
 import kotlin.math.abs
 
+@OptIn(ExperimentalFoundationApi::class)
 internal class LazyListAnimateScrollScope(
     private val state: LazyListState
 ) : LazyLayoutAnimateScrollScope {
@@ -36,20 +38,20 @@
     override val itemCount: Int
         get() = state.layoutInfo.totalItemsCount
 
-    override fun getOffsetForItem(index: Int): Int? =
+    override fun getVisibleItemScrollOffset(index: Int): Int =
         state.layoutInfo.visibleItemsInfo.fastFirstOrNull {
             it.index == index
-        }?.offset
+        }?.offset ?: 0
 
     override fun ScrollScope.snapToItem(index: Int, scrollOffset: Int) {
         state.snapToItemIndexInternal(index, scrollOffset)
     }
 
-    override fun expectedDistanceTo(index: Int, targetScrollOffset: Int): Float {
-        val averageSize = averageItemSize
-        val indexesDiff = index - firstVisibleItemIndex
-        var coercedOffset = minOf(abs(targetScrollOffset), averageSize)
-        if (targetScrollOffset < 0) coercedOffset *= -1
+    override fun calculateDistanceTo(targetIndex: Int, targetItemOffset: Int): Float {
+        val averageSize = visibleItemsAverageSize
+        val indexesDiff = targetIndex - firstVisibleItemIndex
+        var coercedOffset = minOf(abs(targetItemOffset), averageSize)
+        if (targetItemOffset < 0) coercedOffset *= -1
         return (averageSize * indexesDiff).toFloat() +
             coercedOffset - firstVisibleItemScrollOffset
     }
@@ -58,7 +60,7 @@
         state.scroll(block = block)
     }
 
-    override val averageItemSize: Int
+    override val visibleItemsAverageSize: Int
         get() {
             val layoutInfo = state.layoutInfo
             val visibleItems = layoutInfo.visibleItemsInfo
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
index 0f08e81..36572c7 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
@@ -30,6 +30,7 @@
 import androidx.compose.ui.util.fastFilter
 import androidx.compose.ui.util.fastFirstOrNull
 import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastForEachReversed
 import kotlin.math.abs
 import kotlin.math.roundToInt
 import kotlin.math.sign
@@ -473,7 +474,7 @@
         list.add(measuredItemProvider.getAndMeasure(i))
     }
 
-    pinnedItems.fastForEach { index ->
+    pinnedItems.fastForEachReversed { index ->
         if (index < start) {
             if (list == null) list = mutableListOf()
             list?.add(measuredItemProvider.getAndMeasure(index))
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridAnimateScrollScope.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridAnimateScrollScope.kt
index e398593..bf0a56e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridAnimateScrollScope.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridAnimateScrollScope.kt
@@ -16,12 +16,14 @@
 
 package androidx.compose.foundation.lazy.grid
 
+import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.gestures.ScrollScope
 import androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope
 import androidx.compose.ui.util.fastFirstOrNull
 import kotlin.math.abs
 import kotlin.math.max
 
+@OptIn(ExperimentalFoundationApi::class)
 internal class LazyGridAnimateScrollScope(
     private val state: LazyGridState
 ) : LazyLayoutAnimateScrollScope {
@@ -35,7 +37,10 @@
 
     override val itemCount: Int get() = state.layoutInfo.totalItemsCount
 
-    override fun getOffsetForItem(index: Int): Int? =
+    override val visibleItemsAverageSize: Int
+        get() = calculateLineAverageMainAxisSize(state.layoutInfo, state.isVertical)
+
+    override fun getVisibleItemScrollOffset(index: Int): Int =
         state.layoutInfo.visibleItemsInfo
             .fastFirstOrNull {
                 it.index == index
@@ -45,22 +50,22 @@
                 } else {
                     item.offset.x
                 }
-            }
+            } ?: 0
 
     override fun ScrollScope.snapToItem(index: Int, scrollOffset: Int) {
         state.snapToItemIndexInternal(index, scrollOffset)
     }
 
-    override fun expectedDistanceTo(index: Int, targetScrollOffset: Int): Float {
+    override fun calculateDistanceTo(targetIndex: Int, targetItemOffset: Int): Float {
         val slotsPerLine = state.slotsPerLine
-        val averageLineMainAxisSize = averageItemSize
-        val before = index < firstVisibleItemIndex
+        val averageLineMainAxisSize = visibleItemsAverageSize
+        val before = targetIndex < firstVisibleItemIndex
         val linesDiff =
-            (index - firstVisibleItemIndex + (slotsPerLine - 1) * if (before) -1 else 1) /
+            (targetIndex - firstVisibleItemIndex + (slotsPerLine - 1) * if (before) -1 else 1) /
                 slotsPerLine
 
-        var coercedOffset = minOf(abs(targetScrollOffset), averageLineMainAxisSize)
-        if (targetScrollOffset < 0) coercedOffset *= -1
+        var coercedOffset = minOf(abs(targetItemOffset), averageLineMainAxisSize)
+        if (targetItemOffset < 0) coercedOffset *= -1
         return (averageLineMainAxisSize * linesDiff).toFloat() +
             coercedOffset - firstVisibleItemScrollOffset
     }
@@ -112,7 +117,4 @@
     override suspend fun scroll(block: suspend ScrollScope.() -> Unit) {
         state.scroll(block = block)
     }
-
-    override val averageItemSize: Int
-        get() = calculateLineAverageMainAxisSize(state.layoutInfo, state.isVertical)
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt
index 997dcd6..f69ead7 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt
@@ -28,6 +28,7 @@
 import androidx.compose.ui.unit.constrainWidth
 import androidx.compose.ui.util.fastFilter
 import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastForEachReversed
 import androidx.compose.ui.util.fastSumBy
 import kotlin.math.abs
 import kotlin.math.min
@@ -396,7 +397,7 @@
     } else {
         var currentMainAxis = firstLineScrollOffset
 
-        itemsBefore.fastForEach {
+        itemsBefore.fastForEachReversed {
             currentMainAxis -= it.mainAxisSizeWithSpacings
             it.position(currentMainAxis, 0, layoutWidth, layoutHeight)
             positionedItems.add(it)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyAnimateScroll.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyAnimateScroll.kt
index eac8969..538aa2a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyAnimateScroll.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyAnimateScroll.kt
@@ -20,6 +20,7 @@
 import androidx.compose.animation.core.AnimationVector1D
 import androidx.compose.animation.core.animateTo
 import androidx.compose.animation.core.copy
+import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.gestures.ScrollScope
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.dp
@@ -43,30 +44,73 @@
 }
 
 /**
- * Abstraction over animated scroll for using [animateScrollToItem] in different layouts.
- * todo(b/243786897): revisit this API and make it public
+ * A scope to allow customization of animated scroll in LazyLayouts. This scope contains all needed
+ * information to perform an animatedScroll in a scrollable LazyLayout.
  */
+@ExperimentalFoundationApi
 internal interface LazyLayoutAnimateScrollScope {
 
+    /**
+     * The index of the first visible item in the lazy layout.
+     */
     val firstVisibleItemIndex: Int
 
+    /**
+     * The offset of the first visible item.
+     */
     val firstVisibleItemScrollOffset: Int
 
+    /**
+     * The last visible item in the LazyLayout, lastVisibleItemIndex - firstVisibleItemOffset + 1
+     * is the number of visible items.
+     */
     val lastVisibleItemIndex: Int
 
+    /**
+     * The total item count.
+     */
     val itemCount: Int
 
-    val averageItemSize: Int
+    /**
+     * The average size of visible items.
+     */
+    val visibleItemsAverageSize: Int
 
-    fun getOffsetForItem(index: Int): Int?
+    /**
+     * Retrieves the scroll offset for an item that is currently visible.
+     */
+    fun getVisibleItemScrollOffset(index: Int): Int
 
+    /**
+     * Immediately scroll to [index] and settle in [scrollOffset].
+     */
     fun ScrollScope.snapToItem(index: Int, scrollOffset: Int)
 
-    fun expectedDistanceTo(index: Int, targetScrollOffset: Int): Float
+    /**
+     * The "expected" distance to [targetIndex]. This means, how far one needs to scroll to have
+     * [targetIndex] be the [firstVisibleItemIndex] and [firstVisibleItemScrollOffset] be
+     * [targetItemOffset]. In other words, how far one needs to scroll to reach [targetIndex].
+     */
+    fun calculateDistanceTo(targetIndex: Int, targetItemOffset: Int): Float
 
+    /**
+     * Call this function to take control of scrolling and gain the ability to send scroll events
+     * via [ScrollScope.scrollBy] and [ScrollScope.snapToItem]. All actions that change the logical
+     * scroll position must be performed within a [scroll] block (even if they don't call any other
+     * methods on this object) in order to guarantee that mutual exclusion is enforced.
+     *
+     * If [scroll] is called from elsewhere, this will be canceled.
+     */
     suspend fun scroll(block: suspend ScrollScope.() -> Unit)
 }
 
+@Suppress("PrimitiveInLambda")
+@OptIn(ExperimentalFoundationApi::class)
+internal fun LazyLayoutAnimateScrollScope.isItemVisible(index: Int): Boolean {
+    return index in firstVisibleItemIndex..lastVisibleItemIndex
+}
+
+@OptIn(ExperimentalFoundationApi::class)
 internal suspend fun LazyLayoutAnimateScrollScope.animateScrollToItem(
     index: Int,
     scrollOffset: Int,
@@ -82,8 +126,9 @@
             val minDistancePx = with(density) { MinimumDistance.toPx() }
             var loop = true
             var anim = AnimationState(0f)
-            val targetItemInitialOffset = getOffsetForItem(index)
-            if (targetItemInitialOffset != null) {
+
+            if (isItemVisible(index)) {
+                val targetItemInitialOffset = getVisibleItemScrollOffset(index)
                 // It's already visible, just animate directly
                 throw ItemFoundInScroll(targetItemInitialOffset, anim)
             }
@@ -119,7 +164,7 @@
 
             var loops = 1
             while (loop && itemCount > 0) {
-                val expectedDistance = expectedDistanceTo(index, scrollOffset)
+                val expectedDistance = calculateDistanceTo(index, scrollOffset)
                 val target = if (abs(expectedDistance) < targetDistancePx) {
                     val absTargetPx = maxOf(abs(expectedDistance), minDistancePx)
                     if (forward) absTargetPx else -absTargetPx
@@ -140,9 +185,7 @@
                     sequentialAnimation = (anim.velocity != 0f)
                 ) {
                     // If we haven't found the item yet, check if it's visible.
-                    var targetItemOffset = getOffsetForItem(index)
-
-                    if (targetItemOffset == null) {
+                    if (!isItemVisible(index)) {
                         // Springs can overshoot their target, clamp to the desired range
                         val coercedValue = if (target > 0) {
                             value.coerceAtMost(target)
@@ -155,8 +198,7 @@
                         }
 
                         val consumed = scrollBy(delta)
-                        targetItemOffset = getOffsetForItem(index)
-                        if (targetItemOffset != null) {
+                        if (isItemVisible(index)) {
                             debugLog { "Found the item after performing scrollBy()" }
                         } else if (!isOvershot()) {
                             if (delta != consumed) {
@@ -218,7 +260,8 @@
                         loop = false
                         cancelAnimation()
                         return@animateTo
-                    } else if (targetItemOffset != null) {
+                    } else if (isItemVisible(index)) {
+                        val targetItemOffset = getVisibleItemScrollOffset(index)
                         debugLog { "Found item" }
                         throw ItemFoundInScroll(targetItemOffset, anim)
                     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateScrollScope.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateScrollScope.kt
index e16e767..7585ff4 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateScrollScope.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimateScrollScope.kt
@@ -36,10 +36,13 @@
 
     override val itemCount: Int get() = state.layoutInfo.totalItemsCount
 
-    override fun getOffsetForItem(index: Int): Int? =
-        state.layoutInfo.findVisibleItem(index)?.offset?.let {
+    override fun getVisibleItemScrollOffset(index: Int): Int {
+        val searchedIndex = state.layoutInfo.visibleItemsInfo.binarySearch { it.index - index }
+        val item = state.layoutInfo.visibleItemsInfo[searchedIndex]
+        return item.offset.let {
             if (state.isVertical) it.y else it.x
         }
+    }
 
     override fun ScrollScope.snapToItem(index: Int, scrollOffset: Int) {
         with(state) {
@@ -47,12 +50,12 @@
         }
     }
 
-    override fun expectedDistanceTo(index: Int, targetScrollOffset: Int): Float {
-        val averageMainAxisItemSize = averageItemSize
+    override fun calculateDistanceTo(targetIndex: Int, targetItemOffset: Int): Float {
+        val averageMainAxisItemSize = visibleItemsAverageSize
 
-        val lineDiff = index / state.laneCount - firstVisibleItemIndex / state.laneCount
-        var coercedOffset = minOf(abs(targetScrollOffset), averageMainAxisItemSize)
-        if (targetScrollOffset < 0) coercedOffset *= -1
+        val lineDiff = targetIndex / state.laneCount - firstVisibleItemIndex / state.laneCount
+        var coercedOffset = minOf(abs(targetItemOffset), averageMainAxisItemSize)
+        if (targetItemOffset < 0) coercedOffset *= -1
         return averageMainAxisItemSize * lineDiff.toFloat() +
             coercedOffset - firstVisibleItemScrollOffset
     }
@@ -61,7 +64,7 @@
         state.scroll(block = block)
     }
 
-    override val averageItemSize: Int
+    override val visibleItemsAverageSize: Int
         get() {
             val layoutInfo = state.layoutInfo
             val visibleItems = layoutInfo.visibleItemsInfo
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
index cacee1b..131225f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
@@ -30,6 +30,7 @@
 import androidx.compose.ui.unit.constrainWidth
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastForEachIndexed
+import androidx.compose.ui.util.fastForEachReversed
 import androidx.compose.ui.util.fastMaxOfOrNull
 import androidx.compose.ui.util.packInts
 import androidx.compose.ui.util.unpackInt1
@@ -755,7 +756,8 @@
                         firstItemIndices[lane] > itemIndex
                     }
                 }
-            }
+            },
+            beforeVisibleBounds = true
         )
 
         val visibleItems = calculateVisibleItems(
@@ -787,7 +789,8 @@
                         currentItemIndices[lane] < itemIndex
                     }
                 }
-            }
+            },
+            beforeVisibleBounds = false
         )
 
         val positionedItems = mutableListOf<LazyStaggeredGridMeasuredItem>()
@@ -885,11 +888,12 @@
 @ExperimentalFoundationApi
 private inline fun LazyStaggeredGridMeasureContext.calculateExtraItems(
     position: (LazyStaggeredGridMeasuredItem) -> Unit,
-    filter: (itemIndex: Int) -> Boolean
+    filter: (itemIndex: Int) -> Boolean,
+    beforeVisibleBounds: Boolean
 ): List<LazyStaggeredGridMeasuredItem> {
     var result: MutableList<LazyStaggeredGridMeasuredItem>? = null
 
-    pinnedItems.fastForEach { index ->
+    pinnedItems.fastForEach(beforeVisibleBounds) { index ->
         if (filter(index)) {
             val spanRange = itemProvider.getSpanRange(index, 0)
             if (result == null) {
@@ -904,6 +908,10 @@
     return result ?: emptyList()
 }
 
+private inline fun <T> List<T>.fastForEach(reverse: Boolean = false, action: (T) -> Unit) {
+    if (reverse) fastForEachReversed(action) else fastForEach(action)
+}
+
 @JvmInline
 internal value class SpanRange private constructor(val packedValue: Long) {
     constructor(lane: Int, span: Int) : this(packInts(lane, lane + span))
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt
index 6582c25..764eef5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt
@@ -45,6 +45,7 @@
 import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.input.pointer.PointerEventPass
@@ -268,6 +269,7 @@
                 val downEvent =
                     awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Initial)
                 var upEventOrCancellation: PointerInputChange? = null
+                state.upDownDifference = Offset.Zero // Reset
                 while (upEventOrCancellation == null) {
                     val event = awaitPointerEvent(pass = PointerEventPass.Initial)
                     if (event.changes.fastAll { it.changedToUp() }) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerLazyAnimateScrollScope.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerLazyAnimateScrollScope.kt
new file mode 100644
index 0000000..e69ce84
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerLazyAnimateScrollScope.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.pager
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.gestures.ScrollScope
+import androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope
+import androidx.compose.ui.util.fastFirstOrNull
+
+/**
+ * A [LazyLayoutAnimateScrollScope] that allows customization of animated scroll in [Pager].
+ * The scope contains information about the layout where animated scroll can be performed as well as
+ * the necessary tools to do that respecting the scroll mutation priority.
+ *
+ */
+@ExperimentalFoundationApi
+internal fun PagerLazyAnimateScrollScope(state: PagerState): LazyLayoutAnimateScrollScope {
+    return object : LazyLayoutAnimateScrollScope {
+
+        override val firstVisibleItemIndex: Int get() = state.firstVisiblePage
+
+        override val firstVisibleItemScrollOffset: Int get() = state.firstVisiblePageOffset
+
+        override val lastVisibleItemIndex: Int
+            get() =
+                state.layoutInfo.visiblePagesInfo.last().index
+
+        override val itemCount: Int get() = state.pageCount
+
+        override fun getVisibleItemScrollOffset(index: Int): Int {
+            return state.layoutInfo.visiblePagesInfo.fastFirstOrNull { it.index == index }?.offset
+                ?: 0
+        }
+
+        override fun ScrollScope.snapToItem(index: Int, scrollOffset: Int) {
+            state.snapToItem(index, scrollOffset)
+        }
+
+        override fun calculateDistanceTo(targetIndex: Int, targetItemOffset: Int): Float {
+            return (targetIndex - state.currentPage) * visibleItemsAverageSize.toFloat() +
+                targetItemOffset
+        }
+
+        override suspend fun scroll(block: suspend ScrollScope.() -> Unit) {
+            state.scroll(block = block)
+        }
+
+        override val visibleItemsAverageSize: Int
+            get() = state.pageSize + state.pageSpacing
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasurePolicy.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasurePolicy.kt
index 1c8676f..5c6aaf5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasurePolicy.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerMeasurePolicy.kt
@@ -52,15 +52,14 @@
     verticalAlignment: Alignment.Vertical?,
     pageCount: () -> Int,
 ) = remember<LazyLayoutMeasureScope.(Constraints) -> MeasureResult>(
-    contentPadding,
-    pageSpacing,
-    pageSize,
     state,
     contentPadding,
     reverseLayout,
     orientation,
     horizontalAlignment,
     verticalAlignment,
+    pageSpacing,
+    pageSize,
     pageCount,
 ) {
     { containerConstraints ->
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
index b662c3d..de4ecdb 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
@@ -28,7 +28,6 @@
 import androidx.compose.foundation.interaction.InteractionSource
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.lazy.layout.AwaitFirstLayoutModifier
-import androidx.compose.foundation.lazy.layout.LazyLayoutAnimateScrollScope
 import androidx.compose.foundation.lazy.layout.LazyLayoutBeyondBoundsInfo
 import androidx.compose.foundation.lazy.layout.LazyLayoutPinnedItemList
 import androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchState
@@ -151,7 +150,8 @@
      */
     internal var upDownDifference: Offset by mutableStateOf(Offset.Zero)
     internal var snapRemainingScrollOffset by mutableFloatStateOf(0f)
-    private var animateScrollScope = PagerLazyAnimateScrollScope(this)
+
+    private val animatedScrollScope = PagerLazyAnimateScrollScope(this)
 
     private var isScrollingForward: Boolean by mutableStateOf(false)
 
@@ -259,7 +259,7 @@
      */
     val currentPage: Int get() = scrollPosition.currentPage
 
-    private var animationTargetPage by mutableIntStateOf(-1)
+    private var programmaticScrollTargetPage by mutableIntStateOf(-1)
 
     private var settledPageState by mutableIntStateOf(initialPage)
 
@@ -291,8 +291,8 @@
     val targetPage: Int by derivedStateOf(structuralEqualityPolicy()) {
         val finalPage = if (!isScrollInProgress) {
             currentPage
-        } else if (animationTargetPage != -1) {
-            animationTargetPage
+        } else if (programmaticScrollTargetPage != -1) {
+            programmaticScrollTargetPage
         } else if (snapRemainingScrollOffset == 0.0f) {
             // act on scroll only
             if (abs(currentPageOffsetFraction) >= abs(positionThresholdFraction)) {
@@ -399,6 +399,41 @@
         snapToItem(targetPage, offset)
     }
 
+    /**
+     * Jump immediately to a given [page] with a given [pageOffsetFraction] inside
+     * a [ScrollScope]. Use this method to create custom animated scrolling experiences. This will
+     * update the value of [currentPage] and [currentPageOffsetFraction] immediately, but can only
+     * be used inside a [ScrollScope], use [scroll] to gain access to a [ScrollScope].
+     *
+     * Please refer to the sample to learn how to use this API.
+     * @sample androidx.compose.foundation.samples.PagerCustomAnimateScrollToPage
+     *
+     * @param page The destination page to scroll to
+     * @param pageOffsetFraction A fraction of the page size that indicates the offset the
+     * destination page will be offset from its snapped position.
+     */
+    fun ScrollScope.updateCurrentPage(page: Int, pageOffsetFraction: Float = 0.0f) {
+        val targetPageOffsetToSnappedPosition = (pageOffsetFraction * pageAvailableSpace).toInt()
+        with(animatedScrollScope) {
+            snapToItem(page, targetPageOffsetToSnappedPosition)
+        }
+    }
+
+    /**
+     * Used to update [targetPage] during a programmatic scroll operation. This can only be called
+     * inside a [ScrollScope] and should be called anytime a custom scroll (through [scroll]) is
+     * executed in order to correctly update [targetPage]. This will not move the pages and it's
+     * still the responsibility of the caller to call [ScrollScope.scrollBy] in order to actually
+     * get to [targetPage]. By the end of the [scroll] block, when the [Pager] is no longer
+     * scrolling [targetPage] will assume the value of [currentPage].
+     *
+     * Please refer to the sample to learn how to use this API.
+     * @sample androidx.compose.foundation.samples.PagerCustomAnimateScrollToPage
+     */
+    fun ScrollScope.updateTargetPage(targetPage: Int) {
+        programmaticScrollTargetPage = targetPage.coerceInPageRange()
+    }
+
     internal fun snapToItem(page: Int, offset: Int) {
         scrollPosition.requestPosition(page, offset)
         remeasurement?.forceRemeasure()
@@ -431,14 +466,48 @@
             "pageOffsetFraction $pageOffsetFraction is not within the range -0.5 to 0.5"
         }
         val targetPage = page.coerceInPageRange()
-        animationTargetPage = targetPage
         val targetPageOffsetToSnappedPosition = (pageOffsetFraction * pageAvailableSpace).toInt()
-        animateScrollScope.animateScrollToItem(
-            targetPage,
-            targetPageOffsetToSnappedPosition,
-            animationSpec
-        )
-        animationTargetPage = -1
+
+        with(animatedScrollScope) {
+            scroll {
+                updateTargetPage(targetPage)
+                val forward = targetPage > firstVisibleItemIndex
+                val visiblePages = lastVisibleItemIndex - firstVisibleItemIndex + 1
+                if (((forward && targetPage > lastVisibleItemIndex) ||
+                        (!forward && targetPage < firstVisibleItemIndex)) &&
+                    abs(targetPage - firstVisibleItemIndex) >= MaxPagesForAnimateScroll
+                ) {
+                    val preJumpPosition = if (forward) {
+                        (targetPage - visiblePages).coerceAtLeast(firstVisibleItemIndex)
+                    } else {
+                        (targetPage + visiblePages).coerceAtMost(firstVisibleItemIndex)
+                    }
+
+                    debugLog {
+                        "animateScrollToPage with pre-jump to position=$preJumpPosition"
+                    }
+
+                    // Pre-jump to 1 viewport away from destination page, if possible
+                    snapToItem(preJumpPosition, 0)
+                }
+                val pageAvailableSpace = visibleItemsAverageSize
+                val currentPosition = firstVisibleItemIndex
+                val targetOffset = targetPage * pageAvailableSpace
+                val currentOffset = currentPosition * pageAvailableSpace
+
+                // The final delta displacement will be the difference between the pages offsets
+                // discounting whatever offset the original page had scrolled plus the offset
+                // fraction requested by the user.
+                val displacement = (targetOffset - currentOffset -
+                    firstVisibleItemScrollOffset + targetPageOffsetToSnappedPosition).toFloat()
+
+                debugLog { "animateScrollToPage $displacement pixels" }
+                var previousValue = 0f
+                animate(0f, displacement, animationSpec = animationSpec) { currentValue, _ ->
+                    previousValue += scrollBy(currentValue - previousValue)
+                }
+            }
+        }
     }
 
     private suspend fun awaitScrollDependencies() {
@@ -450,7 +519,12 @@
         block: suspend ScrollScope.() -> Unit
     ) {
         awaitScrollDependencies()
+        // will scroll and it's not scrolling already update settled page
+        if (!isScrollInProgress) {
+            settledPageState = currentPage
+        }
         scrollableState.scroll(scrollPriority, block)
+        programmaticScrollTargetPage = -1 // reset animated scroll target page indicator
     }
 
     override fun dispatchRawDelta(delta: Float): Float {
@@ -480,10 +554,6 @@
             result.firstVisiblePageOffset != 0
         numMeasurePasses++
         cancelPrefetchIfVisibleItemsChanged(result)
-        if (!isScrollInProgress) {
-            settledPageState = currentPage
-            upDownDifference = Offset.Zero
-        }
     }
 
     private fun Int.coerceInPageRange() = if (pageCount > 0) {
@@ -660,79 +730,3 @@
         println("PagerState: ${generateMsg()}")
     }
 }
-
-@OptIn(ExperimentalFoundationApi::class)
-private class PagerLazyAnimateScrollScope(val state: PagerState) : LazyLayoutAnimateScrollScope {
-
-    override val firstVisibleItemIndex: Int get() = state.firstVisiblePage
-
-    override val firstVisibleItemScrollOffset: Int get() = state.firstVisiblePageOffset
-
-    override val lastVisibleItemIndex: Int get() = state.layoutInfo.visiblePagesInfo.last().index
-
-    override val itemCount: Int get() = state.pageCount
-
-    override fun getOffsetForItem(index: Int): Int? {
-        return state.layoutInfo.visiblePagesInfo.fastFirstOrNull { it.index == index }?.offset
-    }
-
-    override fun ScrollScope.snapToItem(index: Int, scrollOffset: Int) {
-        state.snapToItem(index, scrollOffset)
-    }
-
-    override fun expectedDistanceTo(index: Int, targetScrollOffset: Int): Float {
-        return (index - state.currentPage) * averageItemSize.toFloat() + targetScrollOffset
-    }
-
-    override suspend fun scroll(block: suspend ScrollScope.() -> Unit) {
-        state.scroll(block = block)
-    }
-
-    override val averageItemSize: Int
-        get() = state.pageSize + state.pageSpacing
-}
-
-private suspend fun LazyLayoutAnimateScrollScope.animateScrollToItem(
-    index: Int,
-    offset: Int,
-    animationSpec: AnimationSpec<Float>
-) {
-    scroll {
-        val forward = index > firstVisibleItemIndex
-        val visiblePages = lastVisibleItemIndex - firstVisibleItemIndex + 1
-        if (((forward && index > lastVisibleItemIndex) ||
-                (!forward && index < firstVisibleItemIndex)) &&
-            abs(index - firstVisibleItemIndex) >= MaxPagesForAnimateScroll
-        ) {
-            val preJumpPosition = if (forward) {
-                (index - visiblePages).coerceAtLeast(firstVisibleItemIndex)
-            } else {
-                (index + visiblePages).coerceAtMost(firstVisibleItemIndex)
-            }
-
-            debugLog {
-                "animateScrollToPage with pre-jump to position=$preJumpPosition"
-            }
-
-            // Pre-jump to 1 viewport away from destination page, if possible
-            snapToItem(preJumpPosition, 0)
-        }
-        val targetPage = index
-        val pageAvailableSpace = averageItemSize
-        val currentPosition = firstVisibleItemIndex
-        val targetOffset = targetPage * pageAvailableSpace
-        val currentOffset = currentPosition * pageAvailableSpace
-
-        // The final delta displacement will be the difference between the pages offsets
-        // discounting whatever offset the original page had scrolled plus the offset
-        // fraction requested by the user.
-        val displacement =
-            (targetOffset - currentOffset - firstVisibleItemScrollOffset + offset).toFloat()
-
-        debugLog { "animateScrollToPage $displacement pixels" }
-        var previousValue = 0f
-        animate(0f, displacement, animationSpec = animationSpec) { currentValue, _ ->
-            previousValue += scrollBy(currentValue - previousValue)
-        }
-    }
-}
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 ff34ae3..248d6d7 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
@@ -49,7 +49,9 @@
     }
 
     open val shouldClip: Boolean
-        get() = textLayoutResult?.layoutInput?.overflow == TextOverflow.Visible
+        get() = textLayoutResult?.let {
+            it.layoutInput.overflow != TextOverflow.Visible && it.hasVisualOverflow
+        } ?: false
 
     // if this copy shows up in traces, this class may become mutable
     fun copy(
@@ -115,6 +117,7 @@
 
     fun updateGlobalPosition(coordinates: LayoutCoordinates) {
         params = params.copy(layoutCoordinates = coordinates)
+        selectionRegistrar.notifyPositionChange(selectableId)
     }
 
     fun draw(drawScope: DrawScope) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
index a02f411..5bf0b16 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
@@ -23,6 +23,7 @@
 import androidx.compose.foundation.text.Handle
 import androidx.compose.foundation.text.TextDragObserver
 import androidx.compose.foundation.text.selection.Selection.AnchorInfo
+import androidx.compose.foundation.text2.input.internal.coerceIn
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.State
 import androidx.compose.runtime.getValue
@@ -50,14 +51,14 @@
 import androidx.compose.ui.platform.TextToolbar
 import androidx.compose.ui.platform.TextToolbarStatus
 import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.buildAnnotatedString
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.util.fastAny
+import androidx.compose.ui.util.fastFilter
 import androidx.compose.ui.util.fastFold
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastForEachIndexed
 import kotlin.math.absoluteValue
-import kotlin.math.max
-import kotlin.math.min
 
 /**
  * A bridge class between user interaction to the text composables for text selection.
@@ -87,7 +88,7 @@
         set(value) {
             if (_isInTouchMode.value != value) {
                 _isInTouchMode.value = value
-                if (value && showToolbar) showSelectionToolbar() else hideSelectionToolbar()
+                updateSelectionToolbar()
             }
         }
 
@@ -162,7 +163,7 @@
                 if (previousPosition != positionInWindow) {
                     previousPosition = positionInWindow
                     updateHandleOffsets()
-                    updateSelectionToolbarPosition()
+                    updateSelectionToolbar()
                 }
             }
         }
@@ -221,12 +222,9 @@
 
     init {
         selectionRegistrar.onPositionChangeCallback = { selectableId ->
-            if (
-                selectableId == selection?.start?.selectableId ||
-                selectableId == selection?.end?.selectableId
-            ) {
+            if (selectableId in selectionRegistrar.subselections) {
                 updateHandleOffsets()
-                updateSelectionToolbarPosition()
+                updateSelectionToolbar()
             }
         }
 
@@ -297,22 +295,26 @@
 
         selectionRegistrar.onSelectableChangeCallback = { selectableKey ->
             if (selectableKey in selectionRegistrar.subselections) {
-                // clear the selection range of each Selectable.
+                // Clear the selection range of each Selectable.
                 onRelease()
                 selection = null
             }
         }
 
-        selectionRegistrar.afterSelectableUnsubscribe = { selectableKey ->
-            if (
-                selectableKey == selection?.start?.selectableId ||
-                selectableKey == selection?.end?.selectableId
-            ) {
+        selectionRegistrar.afterSelectableUnsubscribe = { selectableId ->
+            if (selectableId == selection?.start?.selectableId) {
                 // The selectable that contains a selection handle just unsubscribed.
-                // Hide selection handles for now
+                // Hide the associated selection handle
                 startHandlePosition = null
+            }
+            if (selectableId == selection?.end?.selectableId) {
                 endHandlePosition = null
             }
+
+            if (selectableId in selectionRegistrar.subselections) {
+                // Unsubscribing the selectable may make the selection empty, which would hide it.
+                updateSelectionToolbar()
+            }
         }
     }
 
@@ -331,46 +333,38 @@
         val endSelectable = selection?.end?.let(::getAnchorSelectable)
         val startLayoutCoordinates = startSelectable?.getLayoutCoordinates()
         val endLayoutCoordinates = endSelectable?.getLayoutCoordinates()
+
         if (
             selection == null ||
             containerCoordinates == null ||
             !containerCoordinates.isAttached ||
-            startLayoutCoordinates == null ||
-            endLayoutCoordinates == null
+            (startLayoutCoordinates == null && endLayoutCoordinates == null)
         ) {
             this.startHandlePosition = null
             this.endHandlePosition = null
             return
         }
 
-        val startHandlePosition = containerCoordinates.localPositionOf(
-            startLayoutCoordinates,
-            startSelectable.getHandlePosition(
-                selection = selection,
-                isStartHandle = true
-            )
-        )
-        val endHandlePosition = containerCoordinates.localPositionOf(
-            endLayoutCoordinates,
-            endSelectable.getHandlePosition(
-                selection = selection,
-                isStartHandle = false
-            )
-        )
-
         val visibleBounds = containerCoordinates.visibleBounds()
-
-        // set the new handle position only if the handle is in visible bounds or
-        // the handle is still dragging. If handle goes out of visible bounds during drag, handle
-        // popup is also removed from composition, halting the drag gesture. This affects multiple
-        // text selection when selected text is configured with maxLines=1 and overflow=clip.
-        this.startHandlePosition = startHandlePosition.takeIf {
-            visibleBounds.containsInclusive(startHandlePosition) ||
-                draggingHandle == Handle.SelectionStart
+        this.startHandlePosition = startLayoutCoordinates?.let { handleCoordinates ->
+            // Set the new handle position only if the handle is in visible bounds or
+            // the handle is still dragging. If handle goes out of visible bounds during drag,
+            // handle popup is also removed from composition, halting the drag gesture. This
+            // affects multiple text selection when selected text is configured with maxLines=1
+            // and overflow=clip.
+            val handlePosition = startSelectable.getHandlePosition(selection, isStartHandle = true)
+            val position = containerCoordinates.localPositionOf(handleCoordinates, handlePosition)
+            position.takeIf {
+                draggingHandle == Handle.SelectionStart || visibleBounds.containsInclusive(it)
+            }
         }
-        this.endHandlePosition = endHandlePosition.takeIf {
-            visibleBounds.containsInclusive(endHandlePosition) ||
-                draggingHandle == Handle.SelectionEnd
+
+        this.endHandlePosition = endLayoutCoordinates?.let { handleCoordinates ->
+            val handlePosition = endSelectable.getHandlePosition(selection, isStartHandle = false)
+            val position = containerCoordinates.localPositionOf(handleCoordinates, handlePosition)
+            position.takeIf {
+                draggingHandle == Handle.SelectionEnd || visibleBounds.containsInclusive(it)
+            }
         }
     }
 
@@ -428,61 +422,49 @@
             return false
         }
 
-        var betweenSelectables = false
-        selectionRegistrar.sort(requireContainerCoordinates()).fastForEach {
-            if (
-                it.selectableId != selection.start.selectableId &&
-                it.selectableId != selection.end.selectableId &&
-                !betweenSelectables
-            ) {
-                // haven't found our selection yet, continue
-                return@fastForEach
-            }
-
-            betweenSelectables = true
-            if (!isCurrentSelectionEmpty(selection = selection, selectable = it)) {
-                return true
-            }
-
-            // short-circuit if this is the last selectable
-            if (it.selectableId == selection.end.selectableId && !selection.handlesCrossed ||
-                it.selectableId == selection.start.selectableId && selection.handlesCrossed
-            ) return false
+        if (selection.start.selectableId == selection.end.selectableId) {
+            // Selection is in the same selectable, but not the same anchors,
+            // so there must be some selected text.
+            return true
         }
-        return false
+
+        // All subselections associated with a selectable must be an empty selection.
+        return selectionRegistrar.sort(requireContainerCoordinates()).fastAny { selectable ->
+            selectionRegistrar.subselections[selectable.selectableId]
+                ?.run { start.offset != end.offset }
+                ?: false
+        }
     }
 
     internal fun getSelectedText(): AnnotatedString? {
-        val selectables = selectionRegistrar.sort(requireContainerCoordinates())
-        var selectedText: AnnotatedString? = null
+        if (selection == null || selectionRegistrar.subselections.isEmpty()) {
+            return null
+        }
 
-        selection?.let {
-            for (i in selectables.indices) {
-                val selectable = selectables[i]
-                // Continue if the current selectable is before the selection starts.
-                if (selectable.selectableId != it.start.selectableId &&
-                    selectable.selectableId != it.end.selectableId &&
-                    selectedText == null
-                ) continue
+        return buildAnnotatedString {
+            selectionRegistrar.sort(requireContainerCoordinates()).fastForEach { selectable ->
+                selectionRegistrar.subselections[selectable.selectableId]?.let { subSelection ->
+                    val currentText = selectable.getText()
+                    val currentSelectedText = if (subSelection.handlesCrossed) {
+                        currentText.subSequence(
+                            subSelection.end.offset,
+                            subSelection.start.offset
+                        )
+                    } else {
+                        currentText.subSequence(
+                            subSelection.start.offset,
+                            subSelection.end.offset
+                        )
+                    }
 
-                val currentSelectedText = getCurrentSelectedText(
-                    selectable = selectable,
-                    selection = it
-                )
-                selectedText = selectedText?.plus(currentSelectedText) ?: currentSelectedText
-
-                // Break if the current selectable is the last selected selectable.
-                if (selectable.selectableId == it.end.selectableId && !it.handlesCrossed ||
-                    selectable.selectableId == it.start.selectableId && it.handlesCrossed
-                ) break
+                    append(currentSelectedText)
+                }
             }
         }
-        return selectedText
     }
 
     internal fun copy() {
-        val selectedText = getSelectedText()
-        selectedText?.takeIf { it.isNotEmpty() }?.let { clipboardManager?.setText(it) }
+        getSelectedText()?.takeIf { it.isNotEmpty() }?.let { clipboardManager?.setText(it) }
     }
 
     /**
@@ -494,103 +476,114 @@
     internal var showToolbar = false
         internal set(value) {
             field = value
-            if (value && isInTouchMode) showSelectionToolbar() else hideSelectionToolbar()
+            updateSelectionToolbar()
         }
 
+    private fun toolbarCopy() {
+        copy()
+        onRelease()
+    }
+
+    private fun updateSelectionToolbar() {
+        if (!hasFocus) {
+            return
+        }
+
+        val textToolbar = textToolbar ?: return
+        if (showToolbar && isInTouchMode && isNonEmptySelection()) {
+            val rect = getContentRect() ?: return
+            textToolbar.showMenu(rect = rect, onCopyRequested = ::toolbarCopy)
+        } else if (textToolbar.status == TextToolbarStatus.Shown) {
+            textToolbar.hide()
+        }
+    }
+
     /**
-     * This function get the selected region as a Rectangle region, and pass it to [TextToolbar]
-     * to make the FloatingToolbar show up in the proper place. In addition, this function passes
-     * the copy method as a callback when "copy" is clicked.
+     * Calculate selected region as [Rect].
+     * The result is the smallest [Rect] that encapsulates the entire selection,
+     * coerced into visible bounds.
      */
-    private fun showSelectionToolbar() {
-        if (hasFocus && isNonEmptySelection()) {
-            textToolbar?.showMenu(
-                getContentRect(),
-                onCopyRequested = {
-                    copy()
-                    onRelease()
+    private fun getContentRect(): Rect? {
+        selection ?: return null
+        val containerCoordinates = containerLayoutCoordinates ?: return null
+        if (!containerCoordinates.isAttached) return null
+        val visibleBounds = containerCoordinates.visibleBounds()
+
+        var anyExists = false
+        var rootLeft = Float.POSITIVE_INFINITY
+        var rootTop = Float.POSITIVE_INFINITY
+        var rootRight = Float.NEGATIVE_INFINITY
+        var rootBottom = Float.NEGATIVE_INFINITY
+
+        val sortedSelectables = selectionRegistrar.sort(requireContainerCoordinates())
+            .fastFilter {
+                it.selectableId in selectionRegistrar.subselections
+            }
+
+        if (sortedSelectables.isEmpty()) {
+            return null
+        }
+
+        val selectedSelectables = if (sortedSelectables.size == 1) {
+            sortedSelectables
+        } else {
+            listOf(sortedSelectables.first(), sortedSelectables.last())
+        }
+
+        selectedSelectables.fastForEach { selectable ->
+            val subSelection = selectionRegistrar.subselections[selectable.selectableId]
+                ?: return@fastForEach
+
+            val coordinates = selectable.getLayoutCoordinates()
+                ?: return@fastForEach
+
+            with(subSelection) {
+                if (start.offset == end.offset) {
+                    return@fastForEach
                 }
-            )
+
+                val minOffset = minOf(start.offset, end.offset)
+                val maxOffset = maxOf(start.offset, end.offset)
+
+                var left = Float.POSITIVE_INFINITY
+                var top = Float.POSITIVE_INFINITY
+                var right = Float.NEGATIVE_INFINITY
+                var bottom = Float.NEGATIVE_INFINITY
+                for (i in intArrayOf(minOffset, maxOffset)) {
+                    val rect = selectable.getBoundingBox(i)
+                    left = minOf(left, rect.left)
+                    top = minOf(top, rect.top)
+                    right = maxOf(right, rect.right)
+                    bottom = maxOf(bottom, rect.bottom)
+                }
+
+                val localTopLeft = Offset(left, top)
+                val localBottomRight = Offset(right, bottom)
+
+                val containerTopLeft =
+                    containerCoordinates.localPositionOf(coordinates, localTopLeft)
+                val containerBottomRight =
+                    containerCoordinates.localPositionOf(coordinates, localBottomRight)
+
+                val rootVisibleTopLeft =
+                    containerCoordinates.localToRoot(containerTopLeft.coerceIn(visibleBounds))
+                val rootVisibleBottomRight =
+                    containerCoordinates.localToRoot(containerBottomRight.coerceIn(visibleBounds))
+
+                rootLeft = minOf(rootLeft, rootVisibleTopLeft.x)
+                rootTop = minOf(rootTop, rootVisibleTopLeft.y)
+                rootRight = maxOf(rootRight, rootVisibleBottomRight.x)
+                rootBottom = maxOf(rootBottom, rootVisibleBottomRight.y)
+                anyExists = true
+            }
         }
-    }
 
-    private fun hideSelectionToolbar() {
-        if (hasFocus && textToolbar?.status == TextToolbarStatus.Shown) {
-            textToolbar?.hide()
+        if (!anyExists) {
+            return null
         }
-    }
 
-    private fun updateSelectionToolbarPosition() {
-        if (hasFocus && textToolbar?.status == TextToolbarStatus.Shown) {
-            showSelectionToolbar()
-        }
-    }
-
-    /**
-     * Calculate selected region as [Rect]. The top is the top of the first selected
-     * line, and the bottom is the bottom of the last selected line. The left is the leftmost
-     * handle's horizontal coordinates, and the right is the rightmost handle's coordinates.
-     */
-    private fun getContentRect(): Rect {
-        val selection = selection ?: return Rect.Zero
-        val startSelectable = getAnchorSelectable(selection.start)
-        val endSelectable = getAnchorSelectable(selection.end)
-        val startLayoutCoordinates = startSelectable?.getLayoutCoordinates() ?: return Rect.Zero
-        val endLayoutCoordinates = endSelectable?.getLayoutCoordinates() ?: return Rect.Zero
-
-        val localLayoutCoordinates = containerLayoutCoordinates
-        if (localLayoutCoordinates != null && localLayoutCoordinates.isAttached) {
-            var startOffset = localLayoutCoordinates.localPositionOf(
-                startLayoutCoordinates,
-                startSelectable.getHandlePosition(
-                    selection = selection,
-                    isStartHandle = true
-                )
-            )
-            var endOffset = localLayoutCoordinates.localPositionOf(
-                endLayoutCoordinates,
-                endSelectable.getHandlePosition(
-                    selection = selection,
-                    isStartHandle = false
-                )
-            )
-
-            startOffset = localLayoutCoordinates.localToRoot(startOffset)
-            endOffset = localLayoutCoordinates.localToRoot(endOffset)
-
-            val left = min(startOffset.x, endOffset.x)
-            val right = max(startOffset.x, endOffset.x)
-
-            var startTop = localLayoutCoordinates.localPositionOf(
-                startLayoutCoordinates,
-                Offset(
-                    0f,
-                    startSelectable.getBoundingBox(selection.start.offset).top
-                )
-            )
-
-            var endTop = localLayoutCoordinates.localPositionOf(
-                endLayoutCoordinates,
-                Offset(
-                    0.0f,
-                    endSelectable.getBoundingBox(selection.end.offset).top
-                )
-            )
-
-            startTop = localLayoutCoordinates.localToRoot(startTop)
-            endTop = localLayoutCoordinates.localToRoot(endTop)
-
-            val top = min(startTop.y, endTop.y)
-            val bottom = max(startOffset.y, endOffset.y) + (HandleHeight.value * 4.0).toFloat()
-
-            return Rect(
-                left,
-                top,
-                right,
-                bottom
-            )
-        }
-        return Rect.Zero
+        rootBottom += HandleHeight.value * 4
+        return Rect(rootLeft, rootTop, rootRight, rootBottom)
     }
 
     // This is for PressGestureDetector to cancel the selection.
@@ -985,70 +978,6 @@
     )
 }
 
-private fun isCurrentSelectionEmpty(
-    selectable: Selectable,
-    selection: Selection
-): Boolean {
-    val selectableId = selectable.selectableId
-    val startSelectableId = selection.start.selectableId
-    val endSelectableId = selection.end.selectableId
-
-    if (selectableId == startSelectableId && selectableId == endSelectableId) {
-        return selection.start.offset == selection.end.offset
-    }
-
-    val text = selectable.getText()
-    val handlesCrossed = selection.handlesCrossed
-    return when (selectableId) {
-        startSelectableId -> selection.start.offset == if (handlesCrossed) 0 else text.length
-        endSelectableId -> selection.end.offset == if (handlesCrossed) text.length else 0
-        else -> text.isEmpty()
-    }
-}
-
-internal fun getCurrentSelectedText(
-    selectable: Selectable,
-    selection: Selection
-): AnnotatedString {
-    val currentText = selectable.getText()
-
-    return if (
-        selectable.selectableId != selection.start.selectableId &&
-        selectable.selectableId != selection.end.selectableId
-    ) {
-        // Select the full text content if the current selectable is between the
-        // start and the end selectables.
-        currentText
-    } else if (
-        selectable.selectableId == selection.start.selectableId &&
-        selectable.selectableId == selection.end.selectableId
-    ) {
-        // Select partial text content if the current selectable is the start and
-        // the end selectable.
-        if (selection.handlesCrossed) {
-            currentText.subSequence(selection.end.offset, selection.start.offset)
-        } else {
-            currentText.subSequence(selection.start.offset, selection.end.offset)
-        }
-    } else if (selectable.selectableId == selection.start.selectableId) {
-        // Select partial text content if the current selectable is the start
-        // selectable.
-        if (selection.handlesCrossed) {
-            currentText.subSequence(0, selection.start.offset)
-        } else {
-            currentText.subSequence(selection.start.offset, currentText.length)
-        }
-    } else {
-        // Selectable partial text content if the current selectable is the end
-        // selectable.
-        if (selection.handlesCrossed) {
-            currentText.subSequence(selection.end.offset, currentText.length)
-        } else {
-            currentText.subSequence(0, selection.end.offset)
-        }
-    }
-}
-
 /** Returns the boundary of the visible area in this [LayoutCoordinates]. */
 internal fun LayoutCoordinates.visibleBounds(): Rect {
     // globalBounds is the global boundaries of this LayoutCoordinates after it's clipped by
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/AllCapsTransformation.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/AllCapsTransformation.kt
index 8bc8ce2..ffd4b82 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/AllCapsTransformation.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/AllCapsTransformation.kt
@@ -21,23 +21,24 @@
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.text.input.KeyboardCapitalization
 import androidx.compose.ui.text.intl.Locale
+import androidx.compose.ui.text.substring
 import androidx.compose.ui.text.toUpperCase
 
 /**
  * Returns a [InputTransformation] that forces all text to be uppercase.
  *
- * This filter automatically configures the keyboard to capitalize all characters.
+ * This transformation automatically configures the keyboard to capitalize all characters.
  *
  * @param locale The [Locale] in which to perform the case conversion.
  */
 @ExperimentalFoundationApi
 @Stable
 fun InputTransformation.Companion.allCaps(locale: Locale): InputTransformation =
-    AllCapsFilter(locale)
+    AllCapsTransformation(locale)
 
 // This is a very naive implementation for now, not intended to be production-ready.
 @OptIn(ExperimentalFoundationApi::class)
-private data class AllCapsFilter(private val locale: Locale) : InputTransformation {
+private data class AllCapsTransformation(private val locale: Locale) : InputTransformation {
     override val keyboardOptions = KeyboardOptions(
         capitalization = KeyboardCapitalization.Characters
     )
@@ -46,13 +47,16 @@
         originalValue: TextFieldCharSequence,
         valueWithChanges: TextFieldBuffer
     ) {
-        val selection = valueWithChanges.selectionInCodepoints
-        valueWithChanges.replace(
-            0,
-            valueWithChanges.length,
-            valueWithChanges.toString().toUpperCase(locale)
-        )
-        valueWithChanges.selectCodepointsIn(selection)
+        // only update inserted content
+        valueWithChanges.changes.forEachChange { range, _ ->
+            if (!range.collapsed) {
+                valueWithChanges.replace(
+                    range.min,
+                    range.max,
+                    valueWithChanges.asCharSequence().substring(range).toUpperCase(locale)
+                )
+            }
+        }
     }
 
     override fun toString(): String = "InputTransformation.allCaps(locale=$locale)"
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 1ee0053..80511e30 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
@@ -21,6 +21,7 @@
 import androidx.annotation.VisibleForTesting
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.text2.input.internal.EditingBuffer
+import androidx.compose.foundation.text2.input.internal.undo.TextFieldEditUndoBehavior
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.collection.mutableVectorOf
@@ -59,11 +60,22 @@
  */
 @ExperimentalFoundationApi
 @Stable
-class TextFieldState(
-    initialText: String = "",
-    initialSelectionInChars: TextRange = TextRange.Zero
+class TextFieldState internal constructor(
+    initialText: String,
+    initialSelectionInChars: TextRange,
+    initialTextUndoManager: TextUndoManager
 ) {
 
+    constructor(
+        initialText: String = "",
+        initialSelectionInChars: TextRange = TextRange.Zero
+    ) : this(initialText, initialSelectionInChars, TextUndoManager())
+
+    /**
+     * Manages the history of edit operations that happen in this [TextFieldState].
+     */
+    internal val textUndoManager: TextUndoManager = initialTextUndoManager
+
     /**
      * The editing buffer used for applying editor commands from IME. All edits coming from gestures
      * or IME commands, must be reflected on this buffer eventually.
@@ -116,6 +128,13 @@
     override fun toString(): String =
         "TextFieldState(selectionInChars=${text.selectionInChars}, text=\"$text\")"
 
+    /**
+     * Undo history controller for this TextFieldState.
+     */
+    // TextField does not implement UndoState because Undo related APIs should be able to remain
+    // separately experimental than TextFieldState
+    internal val undoState: UndoState = UndoState(this)
+
     @Suppress("ShowingMemberInHiddenClass")
     @PublishedApi
     internal fun startEdit(value: TextFieldCharSequence): TextFieldBuffer =
@@ -137,6 +156,7 @@
             val finalValue = newValue.toTextFieldCharSequence()
             resetStateAndNotifyIme(finalValue)
         }
+        textUndoManager.clearHistory()
     }
 
     /**
@@ -148,6 +168,11 @@
      * thread, or global snapshot. Also, this function is defined as inline for performance gains,
      * and it's not actually safe to early return from [block].
      *
+     * Also all user edits should be recorded by [textUndoManager] since reverting to a previous
+     * state requires all edit operations to be executed in reverse. However, some commands like
+     * cut, and paste should be atomic operations that do not merge with previous or next operations
+     * in the Undo stack. This can be controlled by [undoBehavior].
+     *
      * @param inputTransformation [InputTransformation] to run after [block] is applied
      * @param notifyImeOfChanges Whether IME should be notified of these changes. Only pass false to
      * this argument if the source of the changes is IME itself.
@@ -156,6 +181,7 @@
     internal inline fun editAsUser(
         inputTransformation: InputTransformation?,
         notifyImeOfChanges: Boolean = true,
+        undoBehavior: TextFieldEditUndoBehavior = TextFieldEditUndoBehavior.MergeIfPossible,
         block: EditingBuffer.() -> Unit
     ) {
         val previousValue = text
@@ -170,12 +196,40 @@
             return
         }
 
-        commitEditAsUser(inputTransformation, notifyImeOfChanges)
+        commitEditAsUser(previousValue, inputTransformation, notifyImeOfChanges, undoBehavior)
+    }
+
+    /**
+     * Edits the contents of this [TextFieldState] without going through an [InputTransformation],
+     * or recording the changes to the [textUndoManager]. IME would still be notified of any changes
+     * committed by [block].
+     *
+     * This method of editing is not recommended for majority of use cases. It is originally added
+     * to support applying of undo/redo actions without clearing the history. Also, it doesn't
+     * allocate an additional buffer like [edit] method because changes are ignored and it's not
+     * a public API.
+     */
+    internal inline fun editWithNoSideEffects(block: EditingBuffer.() -> Unit) {
+        val previousValue = text
+
+        mainBuffer.changeTracker.clearChanges()
+        mainBuffer.block()
+
+        val afterEditValue = TextFieldCharSequence(
+            text = mainBuffer.toString(),
+            selection = mainBuffer.selection,
+            composition = mainBuffer.composition
+        )
+
+        text = afterEditValue
+        notifyIme(previousValue, afterEditValue)
     }
 
     private fun commitEditAsUser(
+        previousValue: TextFieldCharSequence,
         inputTransformation: InputTransformation?,
-        notifyImeOfChanges: Boolean
+        notifyImeOfChanges: Boolean,
+        undoBehavior: TextFieldEditUndoBehavior
     ) {
         val afterEditValue = TextFieldCharSequence(
             text = mainBuffer.toString(),
@@ -189,12 +243,13 @@
             if (notifyImeOfChanges) {
                 notifyIme(oldValue, afterEditValue)
             }
+            recordEditForUndo(previousValue, text, mainBuffer.changeTracker, undoBehavior)
             return
         }
 
         val oldValue = text
 
-        // if only difference is composition, don't run filter
+        // if only difference is composition, don't run filter, don't send it to undo manager
         if (afterEditValue.contentEquals(oldValue) &&
             afterEditValue.selectionInChars == oldValue.selectionInChars
         ) {
@@ -227,6 +282,41 @@
         } else {
             resetStateAndNotifyIme(afterFilterValue)
         }
+        // mutableValue contains all the changes from user and the filter.
+        recordEditForUndo(previousValue, text, mutableValue.changes, undoBehavior)
+    }
+
+    /**
+     * Records the difference between [previousValue] and [postValue], defined by [changes],
+     * into [textUndoManager] according to the strategy defined by [undoBehavior].
+     */
+    private fun recordEditForUndo(
+        previousValue: TextFieldCharSequence,
+        postValue: TextFieldCharSequence,
+        changes: TextFieldBuffer.ChangeList,
+        undoBehavior: TextFieldEditUndoBehavior
+    ) {
+        when (undoBehavior) {
+            TextFieldEditUndoBehavior.ClearHistory -> {
+                textUndoManager.clearHistory()
+            }
+            TextFieldEditUndoBehavior.MergeIfPossible -> {
+                textUndoManager.recordChanges(
+                    pre = previousValue,
+                    post = postValue,
+                    changes = changes,
+                    allowMerge = true
+                )
+            }
+            TextFieldEditUndoBehavior.NeverMerge -> {
+                textUndoManager.recordChanges(
+                    pre = previousValue,
+                    post = postValue,
+                    changes = changes,
+                    allowMerge = false
+                )
+            }
+        }
     }
 
     internal fun addNotifyImeListener(notifyImeListener: NotifyImeListener) {
@@ -325,20 +415,29 @@
     // Preserve nullability since this is public API.
     @Suppress("RedundantNullableReturnType")
     object Saver : androidx.compose.runtime.saveable.Saver<TextFieldState, Any> {
-        override fun SaverScope.save(value: TextFieldState): Any? = listOf(
-            value.text.toString(),
-            value.text.selectionInChars.start,
-            value.text.selectionInChars.end
-        )
+
+        override fun SaverScope.save(value: TextFieldState): Any? {
+            return listOf(
+                value.text.toString(),
+                value.text.selectionInChars.start,
+                value.text.selectionInChars.end,
+                with(TextUndoManager.Companion.Saver) {
+                    save(value.textUndoManager)
+                }
+            )
+        }
 
         override fun restore(value: Any): TextFieldState? {
-            val (text, selectionStart, selectionEnd) = value as List<*>
+            val (text, selectionStart, selectionEnd, savedTextUndoManager) = value as List<*>
             return TextFieldState(
                 initialText = text as String,
                 initialSelectionInChars = TextRange(
                     start = selectionStart as Int,
                     end = selectionEnd as Int
-                )
+                ),
+                initialTextUndoManager = with(TextUndoManager.Companion.Saver) {
+                    restore(savedTextUndoManager!!)
+                }!!
             )
         }
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextUndoManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextUndoManager.kt
new file mode 100644
index 0000000..a080737
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/TextUndoManager.kt
@@ -0,0 +1,266 @@
+/*
+ * 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.text.SNAPSHOTS_INTERVAL_MILLIS
+import androidx.compose.foundation.text2.input.internal.undo.TextDeleteType
+import androidx.compose.foundation.text2.input.internal.undo.TextEditType
+import androidx.compose.foundation.text2.input.internal.undo.TextUndoOperation
+import androidx.compose.foundation.text2.input.internal.undo.UndoManager
+import androidx.compose.foundation.text2.input.internal.undo.redo
+import androidx.compose.foundation.text2.input.internal.undo.undo
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.SaverScope
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.ui.text.substring
+
+/**
+ * An undo manager designed specifically for text editing. This class is mostly responsible for
+ * delegating the incoming undo/redo/record/clear requests to its own [undoManager]. Its most
+ * important task is to keep a separate staging area for incoming text edit operations to possibly
+ * merge them before committing as a single undoable action.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal class TextUndoManager(
+    initialStagingUndo: TextUndoOperation? = null,
+    private val undoManager: UndoManager<TextUndoOperation> = UndoManager(
+        capacity = TEXT_UNDO_CAPACITY
+    )
+) {
+    private var stagingUndo by mutableStateOf<TextUndoOperation?>(initialStagingUndo)
+
+    val canUndo: Boolean
+        get() = undoManager.canUndo || stagingUndo != null
+
+    val canRedo: Boolean
+        get() = undoManager.canRedo && stagingUndo == null
+
+    fun undo(state: TextFieldState) {
+        if (!canUndo) {
+            return
+        }
+
+        flush()
+        state.undo(undoManager.undo())
+    }
+
+    fun redo(state: TextFieldState) {
+        if (!canRedo) {
+            return
+        }
+
+        state.redo(undoManager.redo())
+    }
+
+    fun record(op: TextUndoOperation) {
+        val unobservedStagingUndo = Snapshot.withoutReadObservation { stagingUndo }
+        if (unobservedStagingUndo == null) {
+            stagingUndo = op
+            return
+        }
+
+        val mergedOp = unobservedStagingUndo.merge(op)
+
+        if (mergedOp != null) {
+            // merged operation should replace the top op
+            stagingUndo = mergedOp
+            return
+        }
+
+        // no merge, flush the staging area and put the new operation in
+        flush()
+        stagingUndo = op
+    }
+
+    fun clearHistory() {
+        stagingUndo = null
+        undoManager.clearHistory()
+    }
+
+    private fun flush() {
+        val unobservedStagingUndo = Snapshot.withoutReadObservation { stagingUndo }
+        unobservedStagingUndo?.let { undoManager.record(it) }
+        stagingUndo = null
+    }
+
+    companion object {
+        object Saver : androidx.compose.runtime.saveable.Saver<TextUndoManager, Any> {
+            private val undoManagerSaver = UndoManager.createSaver(TextUndoOperation.Saver)
+
+            override fun SaverScope.save(value: TextUndoManager): Any {
+                return listOf(
+                    value.stagingUndo?.let {
+                        with(TextUndoOperation.Saver) {
+                            save(it)
+                        }
+                    },
+                    with(undoManagerSaver) {
+                        save(value.undoManager)
+                    }
+                )
+            }
+
+            override fun restore(value: Any): TextUndoManager? {
+                val (savedStagingUndo, savedUndoManager) = value as List<*>
+                return TextUndoManager(
+                    savedStagingUndo?.let {
+                        with(TextUndoOperation.Saver) {
+                            restore(it)
+                        }
+                    },
+                    with(undoManagerSaver) {
+                        restore(savedUndoManager!!)
+                    }!!
+                )
+            }
+        }
+    }
+}
+
+/**
+ * Try to merge this [TextUndoOperation] with the [next]. Chronologically the [next] op must
+ * come after this one. If merge is not possible, this function returns null.
+ *
+ * There are many rules that govern the grouping logic of successive undo operations. Here we try
+ * to cover the most basic requirements but this is certainly not an exhaustive list.
+ *
+ * 1. Each action defines whether they can be merged at all. For example, text edits that are
+ * caused by cut or paste define themselves as unmergeable no matter what comes before or next.
+ * 2. If certain amount of time has passed since the latest grouping has begun.
+ * 3. Enter key (hard line break) is unmergeable.
+ * 4. Only same type of text edits can be merged. An insertion must be grouped by other insertions,
+ * a deletion by other deletions. Replace type of edit is never mergeable.
+ *   4.a. Two insertions can only be merged if the chronologically next one is a suffix of the
+ *   previous insertion. In other words, cursor should always be moving forwards.
+ *   4.b. Deletions have directionality. Cursor can only insert in place and move forwards but
+ *   deletion can be requested either forwards (delete) or backwards (backspace). Only deletions
+ *   that have the same direction can be merged. They also have to share a boundary.
+ */
+internal fun TextUndoOperation.merge(next: TextUndoOperation): TextUndoOperation? {
+    if (!canMerge || !next.canMerge) return null
+    // Do not merge if [other] came before this op, or if certain amount of time has passed
+    // between these ops
+    if (
+        next.timeInMillis < timeInMillis ||
+        next.timeInMillis - timeInMillis >= SNAPSHOTS_INTERVAL_MILLIS
+    ) return null
+    // Do not merge undo history when one of the ops is a new line insertion
+    if (isNewLineInsert || next.isNewLineInsert) return null
+    // Only same type of ops can be merged together
+    if (textEditType != next.textEditType) return null
+
+    // only merge insertions if the chronologically next one continuous from the end of
+    // this previous insertion
+    if (textEditType == TextEditType.Insert && index + postText.length == next.index) {
+        return TextUndoOperation(
+            index = index,
+            preText = "",
+            postText = postText + next.postText,
+            preSelection = this.preSelection,
+            postSelection = next.postSelection,
+            timeInMillis = timeInMillis
+        )
+    } else if (textEditType == TextEditType.Delete) {
+        // only merge consecutive deletions if both have the same directionality
+        if (
+            deletionType == next.deletionType &&
+            (deletionType == TextDeleteType.Start || deletionType == TextDeleteType.End)
+        ) {
+            // This op deletes
+            if (index == next.index + next.preText.length) {
+                return TextUndoOperation(
+                    index = next.index,
+                    preText = next.preText + preText,
+                    postText = "",
+                    preSelection = preSelection,
+                    postSelection = next.postSelection,
+                    timeInMillis = timeInMillis
+                )
+            } else if (index == next.index) {
+                return TextUndoOperation(
+                    index = index,
+                    preText = preText + next.preText,
+                    postText = "",
+                    preSelection = preSelection,
+                    postSelection = next.postSelection,
+                    timeInMillis = timeInMillis
+                )
+            }
+        }
+    }
+    return null
+}
+
+/**
+ * Adds the [changes] to this [UndoManager] by converting from [TextFieldBuffer.ChangeList] space
+ * to [TextUndoOperation] space.
+ *
+ * @param pre State of the [TextFieldBuffer] before any changes are applied
+ * @param post State of the [TextFieldBuffer] after all the changes are applied
+ * @param changes List of changes that are applied on [pre] that transforms it to [post].
+ * @param allowMerge Whether to allow merging the calculated operation with the last operation
+ * in the stack.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal fun TextUndoManager.recordChanges(
+    pre: TextFieldCharSequence,
+    post: TextFieldCharSequence,
+    changes: TextFieldBuffer.ChangeList,
+    allowMerge: Boolean = true
+) {
+    // if there are unmerged changes coming from a single edit, force merge all of them to
+    // create a single replace undo operation
+    if (changes.changeCount > 1) {
+        record(
+            TextUndoOperation(
+                index = 0,
+                preText = pre.toString(),
+                postText = post.toString(),
+                preSelection = pre.selectionInChars,
+                postSelection = post.selectionInChars,
+                canMerge = false
+            )
+        )
+    } else if (changes.changeCount == 1) {
+        val preRange = changes.getOriginalRange(0)
+        val postRange = changes.getRange(0)
+        if (!preRange.collapsed || !postRange.collapsed) {
+            record(
+                TextUndoOperation(
+                    index = preRange.min,
+                    preText = pre.substring(preRange),
+                    postText = post.substring(postRange),
+                    preSelection = pre.selectionInChars,
+                    postSelection = post.selectionInChars,
+                    canMerge = allowMerge
+                )
+            )
+        }
+    }
+}
+
+/**
+ * Determines whether this operation is adding a new hard line break. This type of change produces
+ * unmergable [TextUndoOperation].
+ */
+private val TextUndoOperation.isNewLineInsert
+    get() = this.postText == "\n" || this.postText == "\r\n"
+
+private const val TEXT_UNDO_CAPACITY = 100
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/UndoState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/UndoState.kt
new file mode 100644
index 0000000..716d708
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/UndoState.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text2.input
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+
+/**
+ * Defines an interactable undo history.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal class UndoState internal constructor(private val state: TextFieldState) {
+
+    /**
+     * Whether it is possible to execute a meaningful undo action right now. If this value is false,
+     * calling `undo` would be a no-op.
+     */
+    @Suppress("GetterSetterNames")
+    @get:Suppress("GetterSetterNames")
+    val canUndo: Boolean
+        get() = state.textUndoManager.canUndo
+
+    /**
+     * Whether it is possible to execute a meaningful redo action right now. If this value is false,
+     * calling `redo` would be a no-op.
+     */
+    @Suppress("GetterSetterNames")
+    @get:Suppress("GetterSetterNames")
+    val canRedo: Boolean
+        get() = state.textUndoManager.canRedo
+
+    /**
+     * Reverts the latest edit action or a group of actions that are merged together. Calling it
+     * repeatedly can continue undoing the previous actions.
+     */
+    fun undo() {
+        state.textUndoManager.undo(state)
+    }
+
+    /**
+     * Re-applies a change that was previously reverted via [undo].
+     */
+    fun redo() {
+        state.textUndoManager.redo(state)
+    }
+
+    /**
+     * Clears all undo and redo history up to this point.
+     */
+    fun clearHistory() {
+        state.textUndoManager.clearHistory()
+    }
+}
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 ad73c39..8273ced 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
@@ -74,7 +74,7 @@
 
         var i = 0
         var recordedNewChange = false
-        val postDelta = postLength - (preMax - preStart)
+        val postDelta = postLength - (preMax - preMin)
 
         var mergedOverlappingChange: Change? = null
         while (i < _changes.size) {
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 d869ec5..c24a869 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
@@ -144,7 +144,6 @@
      * This function cancels selection if there is any.
      *
      * @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: CharSequence) {
         checkRange(start, end)
@@ -160,8 +159,8 @@
         // the end offset of the editing area for desktop like application. In case of Android,
         // implementation will call setSelection immediately after replace function to update this
         // tentative cursor location.
-        selectionStart = start + text.length
-        selectionEnd = start + text.length
+        selectionStart = min + text.length
+        selectionEnd = min + text.length
 
         // Similarly, if text modification happens, cancel ongoing composition. If caller wants to
         // change the composition text, it is caller's responsibility to call setComposition again
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 a82ea45..1df7c42 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
@@ -217,20 +217,16 @@
                 KeyCommand.SELECT_END -> moveCursorToEnd().selectMovement()
                 KeyCommand.DESELECT -> deselect()
                 KeyCommand.UNDO -> {
-                    // undoManager?.makeSnapshot(value)
-                    // undoManager?.undo()?.let { this@TextFieldKeyInput.onValueChange(it) }
+                    textFieldState.undo()
                 }
-
                 KeyCommand.REDO -> {
-                    // undoManager?.redo()?.let { this@TextFieldKeyInput.onValueChange(it) }
+                    textFieldState.redo()
                 }
-
                 KeyCommand.CHARACTER_PALETTE -> {
                     showCharacterPalette()
                 }
             }
         }
-        // undoManager?.forceNextSnapshot()
         return consumed
     }
 
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 5baac993..b4ce440 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
@@ -88,8 +88,9 @@
     private val text: String = initialValue.toString()
 
     /**
-     * If there is a non-collapsed selection, delete its contents. If the selection is collapsed,
-     * execute the given [or] block.
+     * If there is a non-collapsed selection, delete its contents.
+     *
+     * @return Whether a selected region is deleted.
      */
     fun EditingBuffer.deleteIfSelected(): Boolean {
         if (selection.collapsed) return false
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
index bf2e53e..152ea3c 100644
--- 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
@@ -21,6 +21,7 @@
 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.internal.undo.TextFieldEditUndoBehavior
 import androidx.compose.foundation.text2.input.toVisualText
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.State
@@ -108,7 +109,10 @@
     }
 
     fun deleteSelectedText() {
-        textFieldState.editAsUser(inputTransformation) {
+        textFieldState.editAsUser(
+            inputTransformation,
+            undoBehavior = TextFieldEditUndoBehavior.NeverMerge
+        ) {
             // `selection` is read from the buffer, so we don't need to transform it.
             delete(selection.min, selection.max)
             setSelection(selection.min, selection.min)
@@ -117,9 +121,10 @@
 
     fun replaceSelectedText(
         newText: CharSequence,
-        clearComposition: Boolean = false
+        clearComposition: Boolean = false,
+        undoBehavior: TextFieldEditUndoBehavior = TextFieldEditUndoBehavior.MergeIfPossible
     ) {
-        textFieldState.editAsUser(inputTransformation) {
+        textFieldState.editAsUser(inputTransformation, undoBehavior = undoBehavior) {
             if (clearComposition) {
                 commitComposition()
             }
@@ -150,6 +155,14 @@
         }
     }
 
+    fun undo() {
+        textFieldState.undoState.undo()
+    }
+
+    fun redo() {
+        textFieldState.undoState.redo()
+    }
+
     /**
      * 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
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 a6a69b8..bf4ed35 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
@@ -35,6 +35,7 @@
 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.undo.TextFieldEditUndoBehavior
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -933,7 +934,6 @@
         clipboardManager?.setText(AnnotatedString(text.getSelectedText().toString()))
 
         textFieldState.deleteSelectedText()
-        // TODO(halilibo): undoManager force snapshot
     }
 
     /**
@@ -969,8 +969,10 @@
     fun paste() {
         val clipboardText = clipboardManager?.getText()?.text ?: return
 
-        textFieldState.replaceSelectedText(clipboardText)
-        // TODO(halilibo): undoManager force snapshot
+        textFieldState.replaceSelectedText(
+            clipboardText,
+            undoBehavior = TextFieldEditUndoBehavior.NeverMerge
+        )
     }
 
     /**
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/undo/TextUndoOperation.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/undo/TextUndoOperation.kt
new file mode 100644
index 0000000..ce572cb4
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/undo/TextUndoOperation.kt
@@ -0,0 +1,178 @@
+/*
+ * 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.undo
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text.timeNowMillis
+import androidx.compose.foundation.text2.input.TextFieldState
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.SaverScope
+import androidx.compose.ui.text.TextRange
+
+/**
+ * An undo identifier designed for text editors. Defines a single atomic change that can be applied
+ * directly or in reverse to modify the contents of a text editor.
+ *
+ * @param index Start point of [preText] and [postText].
+ * @param preText Previously written text that's deleted starting from [index].
+ * @param postText New text that's inserted at [index]
+ * @param preSelection Previous selection before changes are applied
+ * @param postSelection New selection after changes are applied
+ * @param timeInMillis When did this change was first committed
+ * @param canMerge Whether this change can be merged with the next or previous change in an undo
+ * stack. There are many other rules that affect the merging strategy between two
+ * [TextUndoOperation]s but this flag is a sure way to force a non-mergeable property.
+ */
+internal class TextUndoOperation(
+    val index: Int,
+    val preText: String,
+    val postText: String,
+    val preSelection: TextRange,
+    val postSelection: TextRange,
+    val timeInMillis: Long = timeNowMillis(),
+    val canMerge: Boolean = true
+) {
+
+    /**
+     * What kind of edit operation is defined by this change. Edit type is decided by forward the
+     * behavior of this change in forward direction (pre -> post).
+     */
+    val textEditType: TextEditType = when {
+        preText.isEmpty() && postText.isEmpty() ->
+            throw IllegalArgumentException("Either pre or post text must not be empty")
+
+        preText.isEmpty() && postText.isNotEmpty() -> TextEditType.Insert
+        preText.isNotEmpty() && postText.isEmpty() -> TextEditType.Delete
+        else -> TextEditType.Replace
+    }
+
+    /**
+     * Only required while deciding whether to merge two deletion type undo operations.
+     */
+    val deletionType: TextDeleteType
+        get() {
+            if (textEditType != TextEditType.Delete) return TextDeleteType.NotByUser
+            if (!postSelection.collapsed) return TextDeleteType.NotByUser
+            if (preSelection.collapsed) {
+                return if (preSelection.start > postSelection.start) {
+                    TextDeleteType.Start
+                } else {
+                    TextDeleteType.End
+                }
+            } else if (preSelection.start == postSelection.start && preSelection.start == index) {
+                return TextDeleteType.Inner
+            }
+            return TextDeleteType.NotByUser
+        }
+
+    companion object {
+
+        val Saver = object : Saver<TextUndoOperation, Any> {
+            override fun SaverScope.save(value: TextUndoOperation): Any = listOf(
+                value.index,
+                value.preText,
+                value.postText,
+                value.preSelection.start,
+                value.preSelection.end,
+                value.postSelection.start,
+                value.postSelection.end,
+                value.timeInMillis
+            )
+
+            override fun restore(value: Any): TextUndoOperation {
+                return with((value as List<*>)) {
+                    TextUndoOperation(
+                        index = get(0) as Int,
+                        preText = get(1) as String,
+                        postText = get(2) as String,
+                        preSelection = TextRange(get(3) as Int, get(4) as Int),
+                        postSelection = TextRange(get(5) as Int, get(6) as Int),
+                        timeInMillis = get(7) as Long,
+                    )
+                }
+            }
+        }
+    }
+}
+
+/**
+ * Apply a given [TextUndoOperation] in reverse to undo this [TextFieldState].
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal fun TextFieldState.undo(op: TextUndoOperation) {
+    editWithNoSideEffects {
+        replace(op.index, op.index + op.postText.length, op.preText)
+        setSelection(op.preSelection.start, op.preSelection.end)
+    }
+}
+
+/**
+ * Apply a given [TextUndoOperation] in forward direction to redo this [TextFieldState].
+ */
+@OptIn(ExperimentalFoundationApi::class)
+internal fun TextFieldState.redo(op: TextUndoOperation) {
+    editWithNoSideEffects {
+        replace(op.index, op.index + op.preText.length, op.postText)
+        setSelection(op.postSelection.start, op.postSelection.end)
+    }
+}
+
+/**
+ * Possible types of a text operation.
+ *
+ * 1. Insert; if the edited range has 0 length, and the new text is longer than 0 length
+ * 2. Delete: if the edited range is longer than 0, and the new text has 0 length
+ * 3. Replace: All other changes.
+ */
+internal enum class TextEditType {
+    Insert, Delete, Replace
+}
+
+/**
+ * When a delete occurs during text editing, it can happen in various shapes.
+ *
+ * 1. Start; When a single character is removed to the start (towards 0) of the cursor, backspace
+ * key behavior.
+ *   "abcd|efg" -> "abc|efg"
+ * 2. End; When a single character is removed to the end (towards length) of the cursor, delete
+ * key behavior.
+ *   "abcd|efg" -> "abcd|fg"
+ * 3. Inner; When a selection of characters are removed, directionless. Both backspace and delete
+ * express the same behavior in this case.
+ *   "ab|cde|fg" -> "ab|fg"
+ * 4. NotByUser; A text editing operation that cannot be executed via a hardware or software
+ * keyboard. For example when a portion of text is removed but it's not next to a cursor or
+ * selection, or selection remains after removal.
+ *   "abcd|efg"  -> "bcd|efg"
+ *   "abc|def|g" -> "a|bc|g"
+ */
+internal enum class TextDeleteType {
+    Start, End, Inner, NotByUser
+}
+
+/**
+ * There are multiple strategies while deciding how to add certain edit operations to undo stack.
+ *   - Normally, merge is decided by UndoOperation's own merge logic, comparing itself to the
+ *   latest operation in the Undo stack.
+ *   - Programmatic updates should clear the history completely.
+ *   - Some atomic actions like cut, and paste shouldn't merge to previous or next actions.
+ */
+internal enum class TextFieldEditUndoBehavior {
+    MergeIfPossible,
+    ClearHistory,
+    NeverMerge
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/undo/UndoManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/undo/UndoManager.kt
new file mode 100644
index 0000000..f127be51
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text2/input/internal/undo/UndoManager.kt
@@ -0,0 +1,173 @@
+/*
+ * 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.undo
+
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.SaverScope
+import androidx.compose.runtime.snapshots.SnapshotStateList
+import androidx.compose.ui.util.fastForEach
+
+/**
+ * A generic purpose undo/redo stack manager.
+ *
+ * @param initialUndoStack Previous undo stack if this manager is being restored from a saved state.
+ * @param initialRedoStack Previous redo stack if this manager is being restored from a saved state.
+ * @param capacity Maximum number of elements that can be hosted by this UndoManager. Total element
+ * count is the sum of undo and redo stack sizes.
+ */
+internal class UndoManager<T>(
+    initialUndoStack: List<T> = emptyList(),
+    initialRedoStack: List<T> = emptyList(),
+    private val capacity: Int = 100
+) {
+
+    private var undoStack = SnapshotStateList<T>().apply {
+        addAll(initialUndoStack)
+    }
+    private var redoStack = SnapshotStateList<T>().apply {
+        addAll(initialRedoStack)
+    }
+
+    internal val canUndo: Boolean
+        get() = undoStack.isNotEmpty()
+
+    internal val canRedo: Boolean
+        get() = redoStack.isNotEmpty()
+
+    val size: Int
+        get() = undoStack.size + redoStack.size
+
+    init {
+        require(capacity >= 0) {
+            "Capacity must be a positive integer"
+        }
+        require(size <= capacity) {
+            "Initial list of undo and redo operations have a size=($size) greater " +
+                "than the given capacity=($capacity)."
+        }
+    }
+
+    fun record(undoableAction: T) {
+        // First clear the redo stack.
+        redoStack.clear()
+
+        while (size > capacity - 1) { // leave room for the immediate `add`
+            undoStack.removeFirst()
+        }
+        undoStack.add(undoableAction)
+    }
+
+    /**
+     * Request undo.
+     *
+     * This method returns the item that was on top of the undo stack. By the time this function
+     * returns, the given item has already been carried to the redo stack.
+     */
+    fun undo(): T {
+        check(canUndo) {
+            "It's an error to call undo while there is nothing to undo. " +
+                "Please first check `canUndo` value before calling the `undo` function."
+        }
+
+        val topOperation = undoStack.removeLast()
+
+        redoStack.add(topOperation)
+        return topOperation
+    }
+
+    /**
+     * Request redo.
+     *
+     * This method returns the item that was on top of the redo stack. By the time this function
+     * returns, the given item has already been carried back to the undo stack.
+     */
+    fun redo(): T {
+        check(canRedo) {
+            "It's an error to call redo while there is nothing to redo. " +
+                "Please first check `canRedo` value before calling the `redo` function."
+        }
+
+        val topOperation = redoStack.removeLast()
+
+        undoStack.add(topOperation)
+        return topOperation
+    }
+
+    fun clearHistory() {
+        undoStack.clear()
+        redoStack.clear()
+    }
+
+    companion object {
+
+        /**
+         * Saver factory for a generic [UndoManager].
+         *
+         * @param itemSaver Since [UndoManager] is defined as a generic class, a specific item saver
+         * is required to _serialize_ each individual item in undo and redo stacks.
+         */
+        inline fun <reified T> createSaver(
+            itemSaver: Saver<T, Any>
+        ) = object : Saver<UndoManager<T>, Any> {
+            /**
+             * Saves the contents of given [value] to a list.
+             *
+             * List's structure is
+             *   - Capacity
+             *   - n; Number of items in undo stack
+             *   - m; Number of items in redo stack
+             *   - n items in order from undo stack
+             *   - m items in order from redo stack
+             */
+            override fun SaverScope.save(value: UndoManager<T>): Any = buildList {
+                add(value.capacity)
+                add(value.undoStack.size)
+                add(value.redoStack.size)
+                value.undoStack.fastForEach {
+                    with(itemSaver) {
+                        add(save(it))
+                    }
+                }
+                value.redoStack.fastForEach {
+                    with(itemSaver) {
+                        add(save(it))
+                    }
+                }
+            }
+
+            @Suppress("UNCHECKED_CAST")
+            override fun restore(value: Any): UndoManager<T> {
+                val list = value as List<Any>
+                val (capacity, undoSize, redoSize) = (list as List<Int>)
+                var i = 3
+                val undoStackItems = buildList {
+                    while (i < undoSize + 3) {
+                        add(itemSaver.restore(list[i])!!)
+                        i++
+                    }
+                }
+                val redoStackItems = buildList {
+                    while (i < undoSize + redoSize + 3) {
+                        add(itemSaver.restore(list[i])!!)
+                        i++
+                    }
+                }
+                return UndoManager(undoStackItems, redoStackItems, capacity)
+            }
+        }
+    }
+}
diff --git a/compose/material/material/api/current.txt b/compose/material/material/api/current.txt
index 989002c..7f4c7216 100644
--- a/compose/material/material/api/current.txt
+++ b/compose/material/material/api/current.txt
@@ -141,20 +141,21 @@
   }
 
   public final class BottomSheetScaffoldKt {
-    method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void BottomSheetScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> sheetContent, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material.BottomSheetScaffoldState scaffoldState, optional kotlin.jvm.functions.Function0<kotlin.Unit>? topBar, 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 sheetGesturesEnabled, optional androidx.compose.ui.graphics.Shape sheetShape, optional float sheetElevation, optional long sheetBackgroundColor, optional long sheetContentColor, optional float sheetPeekHeight, 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 @Deprecated @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void BottomSheetScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> sheetContent, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material.BottomSheetScaffoldState scaffoldState, optional kotlin.jvm.functions.Function0<kotlin.Unit>? topBar, 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 sheetGesturesEnabled, optional androidx.compose.ui.graphics.Shape sheetShape, optional float sheetElevation, optional long sheetBackgroundColor, optional long sheetContentColor, optional float sheetPeekHeight, 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 @androidx.compose.runtime.Composable public static void BottomSheetScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> sheetContent, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material.BottomSheetScaffoldState scaffoldState, optional kotlin.jvm.functions.Function0<kotlin.Unit>? topBar, 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 sheetGesturesEnabled, optional androidx.compose.ui.graphics.Shape sheetShape, optional float sheetElevation, optional long sheetBackgroundColor, optional long sheetContentColor, optional float sheetPeekHeight, optional long backgroundColor, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> content);
     method @Deprecated @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static androidx.compose.material.BottomSheetState BottomSheetScaffoldState(androidx.compose.material.BottomSheetValue initialValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BottomSheetValue,java.lang.Boolean> confirmStateChange);
+    method @Deprecated @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static androidx.compose.material.BottomSheetScaffoldState BottomSheetScaffoldState(androidx.compose.material.DrawerState drawerState, androidx.compose.material.BottomSheetState bottomSheetState, androidx.compose.material.SnackbarHostState snackbarHostState);
     method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Stable public static androidx.compose.material.BottomSheetState BottomSheetState(androidx.compose.material.BottomSheetValue initialValue, androidx.compose.ui.unit.Density density, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.BottomSheetValue,java.lang.Boolean> confirmValueChange);
-    method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.BottomSheetScaffoldState rememberBottomSheetScaffoldState(optional androidx.compose.material.DrawerState drawerState, optional androidx.compose.material.BottomSheetState bottomSheetState, optional androidx.compose.material.SnackbarHostState snackbarHostState);
+    method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.BottomSheetScaffoldState rememberBottomSheetScaffoldState(optional androidx.compose.material.BottomSheetState bottomSheetState, optional androidx.compose.material.SnackbarHostState snackbarHostState);
+    method @Deprecated @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.BottomSheetScaffoldState rememberBottomSheetScaffoldState(optional androidx.compose.material.DrawerState drawerState, optional androidx.compose.material.BottomSheetState bottomSheetState, optional androidx.compose.material.SnackbarHostState snackbarHostState);
     method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.BottomSheetState rememberBottomSheetState(androidx.compose.material.BottomSheetValue initialValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.BottomSheetValue,java.lang.Boolean> confirmStateChange);
   }
 
   @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Stable public final class BottomSheetScaffoldState {
-    ctor public BottomSheetScaffoldState(androidx.compose.material.DrawerState drawerState, androidx.compose.material.BottomSheetState bottomSheetState, androidx.compose.material.SnackbarHostState snackbarHostState);
+    ctor public BottomSheetScaffoldState(androidx.compose.material.BottomSheetState bottomSheetState, androidx.compose.material.SnackbarHostState snackbarHostState);
     method public androidx.compose.material.BottomSheetState getBottomSheetState();
-    method public androidx.compose.material.DrawerState getDrawerState();
     method public androidx.compose.material.SnackbarHostState getSnackbarHostState();
     property public final androidx.compose.material.BottomSheetState bottomSheetState;
-    property public final androidx.compose.material.DrawerState drawerState;
     property public final androidx.compose.material.SnackbarHostState snackbarHostState;
   }
 
diff --git a/compose/material/material/api/restricted_current.txt b/compose/material/material/api/restricted_current.txt
index 989002c..7f4c7216 100644
--- a/compose/material/material/api/restricted_current.txt
+++ b/compose/material/material/api/restricted_current.txt
@@ -141,20 +141,21 @@
   }
 
   public final class BottomSheetScaffoldKt {
-    method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void BottomSheetScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> sheetContent, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material.BottomSheetScaffoldState scaffoldState, optional kotlin.jvm.functions.Function0<kotlin.Unit>? topBar, 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 sheetGesturesEnabled, optional androidx.compose.ui.graphics.Shape sheetShape, optional float sheetElevation, optional long sheetBackgroundColor, optional long sheetContentColor, optional float sheetPeekHeight, 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 @Deprecated @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void BottomSheetScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> sheetContent, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material.BottomSheetScaffoldState scaffoldState, optional kotlin.jvm.functions.Function0<kotlin.Unit>? topBar, 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 sheetGesturesEnabled, optional androidx.compose.ui.graphics.Shape sheetShape, optional float sheetElevation, optional long sheetBackgroundColor, optional long sheetContentColor, optional float sheetPeekHeight, 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 @androidx.compose.runtime.Composable public static void BottomSheetScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> sheetContent, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material.BottomSheetScaffoldState scaffoldState, optional kotlin.jvm.functions.Function0<kotlin.Unit>? topBar, 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 sheetGesturesEnabled, optional androidx.compose.ui.graphics.Shape sheetShape, optional float sheetElevation, optional long sheetBackgroundColor, optional long sheetContentColor, optional float sheetPeekHeight, optional long backgroundColor, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> content);
     method @Deprecated @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static androidx.compose.material.BottomSheetState BottomSheetScaffoldState(androidx.compose.material.BottomSheetValue initialValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super androidx.compose.material.BottomSheetValue,java.lang.Boolean> confirmStateChange);
+    method @Deprecated @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static androidx.compose.material.BottomSheetScaffoldState BottomSheetScaffoldState(androidx.compose.material.DrawerState drawerState, androidx.compose.material.BottomSheetState bottomSheetState, androidx.compose.material.SnackbarHostState snackbarHostState);
     method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Stable public static androidx.compose.material.BottomSheetState BottomSheetState(androidx.compose.material.BottomSheetValue initialValue, androidx.compose.ui.unit.Density density, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.BottomSheetValue,java.lang.Boolean> confirmValueChange);
-    method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.BottomSheetScaffoldState rememberBottomSheetScaffoldState(optional androidx.compose.material.DrawerState drawerState, optional androidx.compose.material.BottomSheetState bottomSheetState, optional androidx.compose.material.SnackbarHostState snackbarHostState);
+    method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.BottomSheetScaffoldState rememberBottomSheetScaffoldState(optional androidx.compose.material.BottomSheetState bottomSheetState, optional androidx.compose.material.SnackbarHostState snackbarHostState);
+    method @Deprecated @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.BottomSheetScaffoldState rememberBottomSheetScaffoldState(optional androidx.compose.material.DrawerState drawerState, optional androidx.compose.material.BottomSheetState bottomSheetState, optional androidx.compose.material.SnackbarHostState snackbarHostState);
     method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.BottomSheetState rememberBottomSheetState(androidx.compose.material.BottomSheetValue initialValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.BottomSheetValue,java.lang.Boolean> confirmStateChange);
   }
 
   @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Stable public final class BottomSheetScaffoldState {
-    ctor public BottomSheetScaffoldState(androidx.compose.material.DrawerState drawerState, androidx.compose.material.BottomSheetState bottomSheetState, androidx.compose.material.SnackbarHostState snackbarHostState);
+    ctor public BottomSheetScaffoldState(androidx.compose.material.BottomSheetState bottomSheetState, androidx.compose.material.SnackbarHostState snackbarHostState);
     method public androidx.compose.material.BottomSheetState getBottomSheetState();
-    method public androidx.compose.material.DrawerState getDrawerState();
     method public androidx.compose.material.SnackbarHostState getSnackbarHostState();
     property public final androidx.compose.material.BottomSheetState bottomSheetState;
-    property public final androidx.compose.material.DrawerState drawerState;
     property public final androidx.compose.material.SnackbarHostState snackbarHostState;
   }
 
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt
index 111e82d..a3c0f64 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt
@@ -23,6 +23,7 @@
 import androidx.compose.material.samples.BackdropScaffoldSample
 import androidx.compose.material.samples.BottomDrawerSample
 import androidx.compose.material.samples.BottomSheetScaffoldSample
+import androidx.compose.material.samples.BottomSheetScaffoldWithDrawerSample
 import androidx.compose.material.samples.ContentAlphaSample
 import androidx.compose.material.samples.CustomAlertDialogSample
 import androidx.compose.material.samples.CustomPullRefreshSample
@@ -52,7 +53,10 @@
         DemoCategory(
             "Bottom Sheets",
             listOf(
-                ComposableDemo("Bottom Sheet") { BottomSheetScaffoldSample() },
+                ComposableDemo("Standard Bottom Sheet") { BottomSheetScaffoldSample() },
+                ComposableDemo("Standard Bottom Sheet with Drawer") {
+                    BottomSheetScaffoldWithDrawerSample()
+                },
                 ComposableDemo("Modal Bottom Sheet") { ModalBottomSheetSample() },
             )
         ),
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BottomSheetScaffoldSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BottomSheetScaffoldSamples.kt
index 116ab3e..cb3994c 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BottomSheetScaffoldSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BottomSheetScaffoldSamples.kt
@@ -27,17 +27,20 @@
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.material.BottomSheetScaffold
 import androidx.compose.material.Button
+import androidx.compose.material.DrawerValue
 import androidx.compose.material.ExperimentalMaterialApi
 import androidx.compose.material.FabPosition
 import androidx.compose.material.FloatingActionButton
 import androidx.compose.material.Icon
 import androidx.compose.material.IconButton
+import androidx.compose.material.ModalDrawer
 import androidx.compose.material.Text
 import androidx.compose.material.TopAppBar
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Favorite
 import androidx.compose.material.icons.filled.Menu
 import androidx.compose.material.rememberBottomSheetScaffoldState
+import androidx.compose.material.rememberDrawerState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -67,13 +70,17 @@
     BottomSheetScaffold(
         sheetContent = {
             Box(
-                Modifier.fillMaxWidth().height(128.dp),
+                Modifier
+                    .fillMaxWidth()
+                    .height(128.dp),
                 contentAlignment = Alignment.Center
             ) {
                 Text("Swipe up to expand sheet")
             }
             Column(
-                Modifier.fillMaxWidth().padding(64.dp),
+                Modifier
+                    .fillMaxWidth()
+                    .padding(64.dp),
                 horizontalAlignment = Alignment.CenterHorizontally
             ) {
                 Text("Sheet content")
@@ -89,14 +96,9 @@
         },
         scaffoldState = scaffoldState,
         topBar = {
-            TopAppBar(
-                title = { Text("Bottom sheet scaffold") },
-                navigationIcon = {
-                    IconButton(onClick = { scope.launch { scaffoldState.drawerState.open() } }) {
-                        Icon(Icons.Default.Menu, contentDescription = "Localized description")
-                    }
-                }
-            )
+            TopAppBar {
+                Text("Bottom sheet scaffold")
+            }
         },
         floatingActionButton = {
             var clickCount by remember { mutableStateOf(0) }
@@ -112,19 +114,7 @@
             }
         },
         floatingActionButtonPosition = FabPosition.End,
-        sheetPeekHeight = 128.dp,
-        drawerContent = {
-            Column(
-                Modifier.fillMaxWidth().padding(16.dp),
-                horizontalAlignment = Alignment.CenterHorizontally
-            ) {
-                Text("Drawer content")
-                Spacer(Modifier.height(20.dp))
-                Button(onClick = { scope.launch { scaffoldState.drawerState.close() } }) {
-                    Text("Click to close drawer")
-                }
-            }
-        }
+        sheetPeekHeight = 128.dp
     ) { innerPadding ->
         LazyColumn(contentPadding = innerPadding) {
             items(100) {
@@ -138,3 +128,95 @@
         }
     }
 }
+
+@Composable
+@OptIn(ExperimentalMaterialApi::class)
+fun BottomSheetScaffoldWithDrawerSample() {
+    val scope = rememberCoroutineScope()
+    val scaffoldState = rememberBottomSheetScaffoldState()
+    val drawerState = rememberDrawerState(DrawerValue.Closed)
+    ModalDrawer(
+        drawerState = drawerState,
+        drawerContent = {
+            Column(
+                Modifier
+                    .fillMaxWidth()
+                    .padding(16.dp),
+                horizontalAlignment = Alignment.CenterHorizontally
+            ) {
+                Text("Drawer content")
+                Spacer(Modifier.height(20.dp))
+                Button(onClick = { scope.launch { drawerState.close() } }) {
+                    Text("Click to close drawer")
+                }
+            }
+        }
+    ) {
+        BottomSheetScaffold(
+            sheetContent = {
+                Box(
+                    Modifier
+                        .fillMaxWidth()
+                        .height(128.dp),
+                    contentAlignment = Alignment.Center
+                ) {
+                    Text("Swipe up to expand sheet")
+                }
+                Column(
+                    Modifier
+                        .fillMaxWidth()
+                        .padding(64.dp),
+                    horizontalAlignment = Alignment.CenterHorizontally
+                ) {
+                    Text("Sheet content")
+                    Spacer(Modifier.height(20.dp))
+                    Button(
+                        onClick = {
+                            scope.launch { scaffoldState.bottomSheetState.collapse() }
+                        }
+                    ) {
+                        Text("Click to collapse sheet")
+                    }
+                }
+            },
+            scaffoldState = scaffoldState,
+            topBar = {
+                TopAppBar(
+                    title = { Text("Bottom sheet scaffold") },
+                    navigationIcon = {
+                        IconButton(onClick = { scope.launch { drawerState.open() } }) {
+                            Icon(Icons.Default.Menu, contentDescription = "Localized description")
+                        }
+                    }
+                )
+            },
+            floatingActionButton = {
+                var clickCount by remember { mutableStateOf(0) }
+                FloatingActionButton(
+                    onClick = {
+                        // show snackbar as a suspend function
+                        scope.launch {
+                            scaffoldState.snackbarHostState
+                                .showSnackbar("Snackbar #${++clickCount}")
+                        }
+                    }
+                ) {
+                    Icon(Icons.Default.Favorite, contentDescription = "Localized description")
+                }
+            },
+            floatingActionButtonPosition = FabPosition.End,
+            sheetPeekHeight = 128.dp
+        ) { innerPadding ->
+            LazyColumn(contentPadding = innerPadding) {
+                items(100) {
+                    Box(
+                        Modifier
+                            .fillMaxWidth()
+                            .height(50.dp)
+                            .background(colors[it % colors.size])
+                    )
+                }
+            }
+        }
+    }
+}
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomSheetScaffold.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomSheetScaffold.kt
index c3d1131..2318e39 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomSheetScaffold.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/BottomSheetScaffold.kt
@@ -325,14 +325,12 @@
 /**
  * State of the [BottomSheetScaffold] composable.
  *
- * @param drawerState The state of the navigation drawer.
  * @param bottomSheetState The state of the persistent bottom sheet.
  * @param snackbarHostState The [SnackbarHostState] used to show snackbars inside the scaffold.
  */
 @ExperimentalMaterialApi
 @Stable
 class BottomSheetScaffoldState(
-    val drawerState: DrawerState,
     val bottomSheetState: BottomSheetState,
     val snackbarHostState: SnackbarHostState
 )
@@ -340,22 +338,177 @@
 /**
  * Create and [remember] a [BottomSheetScaffoldState].
  *
+ * @param bottomSheetState The state of the persistent bottom sheet.
+ * @param snackbarHostState The [SnackbarHostState] used to show snackbars inside the scaffold.
+ */
+@Composable
+@ExperimentalMaterialApi
+fun rememberBottomSheetScaffoldState(
+    bottomSheetState: BottomSheetState = rememberBottomSheetState(Collapsed),
+    snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }
+): BottomSheetScaffoldState {
+    return remember(bottomSheetState, snackbarHostState) {
+        BottomSheetScaffoldState(
+            bottomSheetState = bottomSheetState,
+            snackbarHostState = snackbarHostState
+        )
+    }
+}
+
+/**
+ * State of the [BottomSheetScaffold] composable.
+ *
+ * @param drawerState The state of the navigation drawer.
+ * @param bottomSheetState The state of the persistent bottom sheet.
+ * @param snackbarHostState The [SnackbarHostState] used to show snackbars inside the scaffold.
+ */
+@Suppress("UNUSED_PARAMETER")
+@Deprecated(message = BottomSheetScaffoldWithDrawerDeprecated, level = DeprecationLevel.ERROR)
+@ExperimentalMaterialApi
+fun BottomSheetScaffoldState(
+    drawerState: DrawerState,
+    bottomSheetState: BottomSheetState,
+    snackbarHostState: SnackbarHostState
+): BottomSheetScaffoldState = error(BottomSheetScaffoldWithDrawerDeprecated)
+
+/**
+ * Create and [remember] a [BottomSheetScaffoldState].
+ *
  * @param drawerState The state of the navigation drawer.
  * @param bottomSheetState The state of the persistent bottom sheet.
  * @param snackbarHostState The [SnackbarHostState] used to show snackbars inside the scaffold.
  */
+@Suppress("UNUSED_PARAMETER")
+@Deprecated(message = BottomSheetScaffoldWithDrawerDeprecated, level = DeprecationLevel.ERROR)
 @Composable
 @ExperimentalMaterialApi
 fun rememberBottomSheetScaffoldState(
     drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
     bottomSheetState: BottomSheetState = rememberBottomSheetState(Collapsed),
     snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }
-): BottomSheetScaffoldState {
-    return remember(drawerState, bottomSheetState, snackbarHostState) {
-        BottomSheetScaffoldState(
-            drawerState = drawerState,
-            bottomSheetState = bottomSheetState,
-            snackbarHostState = snackbarHostState
+): BottomSheetScaffoldState = error(BottomSheetScaffoldWithDrawerDeprecated)
+
+/**
+ * <a href="https://material.io/components/sheets-bottom#standard-bottom-sheet" class="external" target="_blank">Material Design standard bottom sheet</a>.
+ *
+ * Standard bottom sheets co-exist with the screen’s main UI region and allow for simultaneously
+ * viewing and interacting with both regions. They are commonly used to keep a feature or
+ * secondary content visible on screen when content in main UI region is frequently scrolled or
+ * panned.
+ *
+ * ![Standard bottom sheet image](https://developer.android.com/images/reference/androidx/compose/material/standard-bottom-sheet.png)
+ *
+ * This component provides an API to put together several material components to construct your
+ * screen. For a similar component which implements the basic material design layout strategy
+ * with app bars, floating action buttons and navigation drawers, use the standard [Scaffold].
+ * For similar component that uses a backdrop as the centerpiece of the screen, use
+ * [BackdropScaffold].
+ *
+ * A simple example of a bottom sheet scaffold looks like this:
+ *
+ * @sample androidx.compose.material.samples.BottomSheetScaffoldSample
+ *
+ * @param sheetContent The content of the bottom sheet.
+ * @param modifier An optional [Modifier] for the root of the scaffold.
+ * @param scaffoldState The state of the scaffold.
+ * @param topBar An optional top app bar.
+ * @param snackbarHost The composable hosting the snackbars shown inside the scaffold.
+ * @param floatingActionButton An optional floating action button.
+ * @param floatingActionButtonPosition The position of the floating action button.
+ * @param sheetGesturesEnabled Whether the bottom sheet can be interacted with by gestures.
+ * @param sheetShape The shape of the bottom sheet.
+ * @param sheetElevation The elevation of the bottom sheet.
+ * @param sheetBackgroundColor The background color of the bottom sheet.
+ * @param sheetContentColor The preferred content color provided by the bottom sheet to its
+ * children. Defaults to the matching content color for [sheetBackgroundColor], or if that is
+ * not a color from the theme, this will keep the same content color set above the bottom sheet.
+ * @param sheetPeekHeight The height of the bottom sheet when it is collapsed. If the peek height
+ * equals the sheet's full height, the sheet will only have a collapsed state.
+ * @param backgroundColor The background color of the scaffold body.
+ * @param contentColor The color of the content in scaffold body. Defaults to either the matching
+ * content color for [backgroundColor], or, if it is not a color from the theme, this will keep
+ * the same value set above this Surface.
+ * @param content The main content of the screen. You should use the provided [PaddingValues]
+ * to properly offset the content, so that it is not obstructed by the bottom sheet when collapsed.
+ */
+@Composable
+@ExperimentalMaterialApi
+fun BottomSheetScaffold(
+    sheetContent: @Composable ColumnScope.() -> Unit,
+    modifier: Modifier = Modifier,
+    scaffoldState: BottomSheetScaffoldState = rememberBottomSheetScaffoldState(),
+    topBar: (@Composable () -> Unit)? = null,
+    snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
+    floatingActionButton: (@Composable () -> Unit)? = null,
+    floatingActionButtonPosition: FabPosition = FabPosition.End,
+    sheetGesturesEnabled: Boolean = true,
+    sheetShape: Shape = MaterialTheme.shapes.large,
+    sheetElevation: Dp = BottomSheetScaffoldDefaults.SheetElevation,
+    sheetBackgroundColor: Color = MaterialTheme.colors.surface,
+    sheetContentColor: Color = contentColorFor(sheetBackgroundColor),
+    sheetPeekHeight: Dp = BottomSheetScaffoldDefaults.SheetPeekHeight,
+    backgroundColor: Color = MaterialTheme.colors.background,
+    contentColor: Color = contentColorFor(backgroundColor),
+    content: @Composable (PaddingValues) -> Unit
+) {
+    // b/278692145 Remove this once deprecated methods without density are removed
+    val density = LocalDensity.current
+    SideEffect {
+        scaffoldState.bottomSheetState.density = density
+    }
+
+    val peekHeightPx = with(LocalDensity.current) { sheetPeekHeight.toPx() }
+    Surface(
+        modifier
+            .fillMaxSize(),
+        color = backgroundColor,
+        contentColor = contentColor
+    ) {
+        BottomSheetScaffoldLayout(
+            topBar = topBar,
+            body = content,
+            bottomSheet = { layoutHeight ->
+                val nestedScroll = if (sheetGesturesEnabled) {
+                    Modifier
+                        .nestedScroll(
+                            remember(scaffoldState.bottomSheetState.anchoredDraggableState) {
+                                ConsumeSwipeWithinBottomSheetBoundsNestedScrollConnection(
+                                    state = scaffoldState.bottomSheetState.anchoredDraggableState,
+                                    orientation = Orientation.Vertical
+                                )
+                            }
+                        )
+                } else Modifier
+                BottomSheet(
+                    state = scaffoldState.bottomSheetState,
+                    modifier = nestedScroll
+                        .fillMaxWidth()
+                        .requiredHeightIn(min = sheetPeekHeight),
+                    calculateAnchors = { sheetSize ->
+                        val sheetHeight = sheetSize.height.toFloat()
+                        DraggableAnchors {
+                            Collapsed at layoutHeight - peekHeightPx
+                            if (sheetHeight > 0f && sheetHeight != peekHeightPx) {
+                                Expanded at layoutHeight - sheetHeight
+                            }
+                        }
+                    },
+                    sheetBackgroundColor = sheetBackgroundColor,
+                    sheetContentColor = sheetContentColor,
+                    sheetElevation = sheetElevation,
+                    sheetGesturesEnabled = sheetGesturesEnabled,
+                    sheetShape = sheetShape,
+                    content = sheetContent
+                )
+            },
+            floatingActionButton = floatingActionButton,
+            snackbarHost = {
+                snackbarHost(scaffoldState.snackbarHostState)
+            },
+            sheetOffset = { scaffoldState.bottomSheetState.requireOffset() },
+            sheetPeekHeight = sheetPeekHeight,
+            sheetState = scaffoldState.bottomSheetState,
+            floatingActionButtonPosition = floatingActionButtonPosition
         )
     }
 }
@@ -405,9 +558,15 @@
  * children. Defaults to the matching content color for [drawerBackgroundColor], or if that is
  * not a color from the theme, this will keep the same content color set above the drawer sheet.
  * @param drawerScrimColor The color of the scrim that is applied when the drawer is open.
+ * @param backgroundColor The background color of the scaffold body.
+ * @param contentColor The color of the content in scaffold body. Defaults to either the matching
+ * content color for [backgroundColor], or, if it is not a color from the theme, this will keep
+ * the same value set above this Surface.
  * @param content The main content of the screen. You should use the provided [PaddingValues]
  * to properly offset the content, so that it is not obstructed by the bottom sheet when collapsed.
  */
+@Suppress("UNUSED_PARAMETER")
+@Deprecated(message = BottomSheetScaffoldWithDrawerDeprecated, level = DeprecationLevel.ERROR)
 @Composable
 @ExperimentalMaterialApi
 fun BottomSheetScaffold(
@@ -435,83 +594,7 @@
     contentColor: Color = contentColorFor(backgroundColor),
     content: @Composable (PaddingValues) -> Unit
 ) {
-    // b/278692145 Remove this once deprecated methods without density are removed
-    val density = LocalDensity.current
-    SideEffect {
-        scaffoldState.bottomSheetState.density = density
-    }
-
-    val peekHeightPx = with(LocalDensity.current) { sheetPeekHeight.toPx() }
-    val child = @Composable {
-        BottomSheetScaffoldLayout(
-            topBar = topBar,
-            body = content,
-            bottomSheet = { layoutHeight ->
-                val nestedScroll = if (sheetGesturesEnabled) {
-                    Modifier
-                        .nestedScroll(
-                            remember(scaffoldState.bottomSheetState.anchoredDraggableState) {
-                                ConsumeSwipeWithinBottomSheetBoundsNestedScrollConnection(
-                                    state = scaffoldState.bottomSheetState.anchoredDraggableState,
-                                    orientation = Orientation.Vertical
-                                )
-                            }
-                        )
-                } else Modifier
-                BottomSheet(
-                    state = scaffoldState.bottomSheetState,
-                    modifier = nestedScroll
-                        .fillMaxWidth()
-                        .requiredHeightIn(min = sheetPeekHeight),
-                    calculateAnchors = { sheetSize ->
-                        val sheetHeight = sheetSize.height.toFloat()
-                        DraggableAnchors {
-                            Collapsed at layoutHeight - peekHeightPx
-                            if (sheetHeight > 0f && sheetHeight != peekHeightPx) {
-                                Expanded at layoutHeight - sheetHeight
-                            }
-                        }
-                    },
-                    sheetBackgroundColor = sheetBackgroundColor,
-                    sheetContentColor = sheetContentColor,
-                    sheetElevation = sheetElevation,
-                    sheetGesturesEnabled = sheetGesturesEnabled,
-                    sheetShape = sheetShape,
-                    content = sheetContent
-                )
-            },
-            floatingActionButton = floatingActionButton,
-            snackbarHost = {
-                snackbarHost(scaffoldState.snackbarHostState)
-            },
-            sheetOffset = { scaffoldState.bottomSheetState.requireOffset() },
-            sheetPeekHeight = sheetPeekHeight,
-            sheetState = scaffoldState.bottomSheetState,
-            floatingActionButtonPosition = floatingActionButtonPosition
-        )
-    }
-    Surface(
-        modifier
-            .fillMaxSize(),
-        color = backgroundColor,
-        contentColor = contentColor
-    ) {
-        if (drawerContent == null) {
-            child()
-        } else {
-            ModalDrawer(
-                drawerContent = drawerContent,
-                drawerState = scaffoldState.drawerState,
-                gesturesEnabled = drawerGesturesEnabled,
-                drawerShape = drawerShape,
-                drawerElevation = drawerElevation,
-                drawerBackgroundColor = drawerBackgroundColor,
-                drawerContentColor = drawerContentColor,
-                scrimColor = drawerScrimColor,
-                content = child
-            )
-        }
-    }
+    error(BottomSheetScaffoldWithDrawerDeprecated)
 }
 
 @OptIn(ExperimentalMaterialApi::class)
@@ -723,3 +806,6 @@
 private val FabSpacing = 16.dp
 private val BottomSheetScaffoldPositionalThreshold = 56.dp
 private val BottomSheetScaffoldVelocityThreshold = 125.dp
+private const val BottomSheetScaffoldWithDrawerDeprecated = "BottomSheetScaffold with a drawer " +
+    "has been deprecated. To achieve the same functionality, wrap your BottomSheetScaffold in a" +
+    "ModalDrawer. See BottomSheetScaffoldWithDrawerSample for more details."
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 82b469d..1c435af 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -336,7 +336,7 @@
     method @androidx.compose.runtime.Composable public static void SuggestionChip(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.ChipColors colors, optional androidx.compose.material3.ChipElevation? elevation, optional androidx.compose.material3.ChipBorder? border, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
   }
 
-  @androidx.compose.runtime.Stable public final class ColorScheme {
+  @androidx.compose.runtime.Immutable public final class ColorScheme {
     ctor public ColorScheme(long primary, long onPrimary, long primaryContainer, long onPrimaryContainer, long inversePrimary, long secondary, long onSecondary, long secondaryContainer, long onSecondaryContainer, long tertiary, long onTertiary, long tertiaryContainer, long onTertiaryContainer, long background, long onBackground, long surface, long onSurface, long surfaceVariant, long onSurfaceVariant, long surfaceTint, long inverseSurface, long inverseOnSurface, long error, long onError, long errorContainer, long onErrorContainer, long outline, long outlineVariant, long scrim);
     ctor public ColorScheme(long primary, long onPrimary, long primaryContainer, long onPrimaryContainer, long inversePrimary, long secondary, long onSecondary, long secondaryContainer, long onSecondaryContainer, long tertiary, long onTertiary, long tertiaryContainer, long onTertiaryContainer, long background, long onBackground, long surface, long onSurface, long surfaceVariant, long onSurfaceVariant, long surfaceTint, long inverseSurface, long inverseOnSurface, long error, long onError, long errorContainer, long onErrorContainer, long outline, long outlineVariant, long scrim, long surfaceBright, long surfaceDim, long surfaceContainer, long surfaceContainerHigh, long surfaceContainerHighest, long surfaceContainerLow, long surfaceContainerLowest);
     method @Deprecated public androidx.compose.material3.ColorScheme copy(optional long primary, optional long onPrimary, optional long primaryContainer, optional long onPrimaryContainer, optional long inversePrimary, optional long secondary, optional long onSecondary, optional long secondaryContainer, optional long onSecondaryContainer, optional long tertiary, optional long onTertiary, optional long tertiaryContainer, optional long onTertiaryContainer, optional long background, optional long onBackground, optional long surface, optional long onSurface, optional long surfaceVariant, optional long onSurfaceVariant, optional long surfaceTint, optional long inverseSurface, optional long inverseOnSurface, optional long error, optional long onError, optional long errorContainer, optional long onErrorContainer, optional long outline, optional long outlineVariant, optional long scrim);
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 82b469d..1c435af 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -336,7 +336,7 @@
     method @androidx.compose.runtime.Composable public static void SuggestionChip(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> label, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.ChipColors colors, optional androidx.compose.material3.ChipElevation? elevation, optional androidx.compose.material3.ChipBorder? border, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
   }
 
-  @androidx.compose.runtime.Stable public final class ColorScheme {
+  @androidx.compose.runtime.Immutable public final class ColorScheme {
     ctor public ColorScheme(long primary, long onPrimary, long primaryContainer, long onPrimaryContainer, long inversePrimary, long secondary, long onSecondary, long secondaryContainer, long onSecondaryContainer, long tertiary, long onTertiary, long tertiaryContainer, long onTertiaryContainer, long background, long onBackground, long surface, long onSurface, long surfaceVariant, long onSurfaceVariant, long surfaceTint, long inverseSurface, long inverseOnSurface, long error, long onError, long errorContainer, long onErrorContainer, long outline, long outlineVariant, long scrim);
     ctor public ColorScheme(long primary, long onPrimary, long primaryContainer, long onPrimaryContainer, long inversePrimary, long secondary, long onSecondary, long secondaryContainer, long onSecondaryContainer, long tertiary, long onTertiary, long tertiaryContainer, long onTertiaryContainer, long background, long onBackground, long surface, long onSurface, long surfaceVariant, long onSurfaceVariant, long surfaceTint, long inverseSurface, long inverseOnSurface, long error, long onError, long errorContainer, long onErrorContainer, long outline, long outlineVariant, long scrim, long surfaceBright, long surfaceDim, long surfaceContainer, long surfaceContainerHigh, long surfaceContainerHighest, long surfaceContainerLow, long surfaceContainerLowest);
     method @Deprecated public androidx.compose.material3.ColorScheme copy(optional long primary, optional long onPrimary, optional long primaryContainer, optional long onPrimaryContainer, optional long inversePrimary, optional long secondary, optional long onSecondary, optional long secondaryContainer, optional long onSecondaryContainer, optional long tertiary, optional long onTertiary, optional long tertiaryContainer, optional long onTertiaryContainer, optional long background, optional long onBackground, optional long surface, optional long onSurface, optional long surfaceVariant, optional long onSurfaceVariant, optional long surfaceTint, optional long inverseSurface, optional long inverseOnSurface, optional long error, optional long onError, optional long errorContainer, optional long onErrorContainer, optional long outline, optional long outlineVariant, optional long scrim);
diff --git a/compose/material3/material3/lint-baseline.xml b/compose/material3/material3/lint-baseline.xml
index 01aabf1..bf09410 100644
--- a/compose/material3/material3/lint-baseline.xml
+++ b/compose/material3/material3/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.2.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.2.0-beta01)" variant="all" version="8.2.0-beta01">
+<issues format="6" by="lint 8.3.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
 
     <issue
         id="BanThreadSleep"
@@ -173,15 +173,6 @@
     </issue>
 
     <issue
-        id="ExperimentalPropertyAnnotation"
-        message="This property does not have all required annotations to correctly mark it as experimental."
-        errorLine1="    @ExperimentalMaterial3Api"
-        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material3/SwipeableV2.kt"/>
-    </issue>
-
-    <issue
         id="PrimitiveInLambda"
         message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method BottomSheetScaffoldLayout has parameter &apos;bottomSheet&apos; with type Function1&lt;? super Integer, Unit>."
         errorLine1="    bottomSheet: @Composable (layoutHeight: Int) -> Unit,"
@@ -201,15 +192,6 @@
 
     <issue
         id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method BottomSheetScaffoldAnchorChangeHandler has parameter &apos;animateTo&apos; with type Function2&lt;? super SheetValue, ? super Float, Unit>."
-        errorLine1="    animateTo: (target: SheetValue, velocity: Float) -> Unit,"
-        errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material3/BottomSheetScaffold.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
         message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method DateInputContent has parameter &apos;onDateSelectionChange&apos; with type Function1&lt;? super Long, Unit>."
         errorLine1="    onDateSelectionChange: (dateInMillis: Long?) -> Unit,"
         errorLine2="                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -382,8 +364,8 @@
     <issue
         id="PrimitiveInLambda"
         message="Use a functional interface instead of lambda syntax for lambdas with primitive values in variable &apos;onDateSelectionChange&apos; with type Function1&lt;? super Long, ? extends Unit>."
-        errorLine1="                    val onDateSelectionChange = { dateInMillis: Long ->"
-        errorLine2="                    ^">
+        errorLine1="        val onDateSelectionChange = { dateInMillis: Long ->"
+        errorLine2="        ^">
         <location
             file="src/commonMain/kotlin/androidx/compose/material3/DateRangePicker.kt"/>
     </issue>
@@ -417,24 +399,6 @@
 
     <issue
         id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method modalBottomSheetSwipeable has parameter &apos;onDragStopped&apos; with type Function2&lt;? super CoroutineScope, ? super Float, Unit>."
-        errorLine1="    onDragStopped: CoroutineScope.(velocity: Float) -> Unit,"
-        errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method ModalBottomSheetAnchorChangeHandler has parameter &apos;animateTo&apos; with type Function2&lt;? super SheetValue, ? super Float, Unit>."
-        errorLine1="    animateTo: (target: SheetValue, velocity: Float) -> Unit,"
-        errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
         message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method Scrim has parameter &apos;fraction&apos; with type Function0&lt;Float>."
         errorLine1="    fraction: () -> Float,"
         errorLine2="              ~~~~~~~~~~~">
@@ -534,24 +498,6 @@
 
     <issue
         id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in constructor SliderDraggableState has parameter &apos;onDelta&apos; with type Function1&lt;? super Float, Unit>."
-        errorLine1="    val onDelta: (Float) -> Unit"
-        errorLine2="                 ~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material3/Slider.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in return type Function1&lt;Float, Unit> of &apos;getOnDelta&apos;."
-        errorLine1="    val onDelta: (Float) -> Unit"
-        errorLine2="                 ~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material3/Slider.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
         message="Use a functional interface instead of lambda syntax for lambdas with primitive values in constructor SliderState has parameter &apos;initialOnValueChange&apos; with type Function1&lt;? super Float, Unit>."
         errorLine1="    initialOnValueChange: ((Float) -> Unit)? = null,"
         errorLine2="                          ~~~~~~~~~~~~~~~~~~">
@@ -579,15 +525,6 @@
 
     <issue
         id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in return type Function3&lt;PressGestureScope, Offset, Continuation&lt;? super Unit>, Object> of &apos;getPress$lint_module&apos;."
-        errorLine1="    internal val press: suspend PressGestureScope.(Offset) -> Unit = { pos ->"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material3/Slider.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
         message="Use a functional interface instead of lambda syntax for lambdas with primitive values in constructor RangeSliderState has parameter &apos;initialOnValueChange&apos; with type Function1&lt;? super FloatRange, Unit>."
         errorLine1="    initialOnValueChange: ((FloatRange) -> Unit)? = null,"
         errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~">
@@ -615,15 +552,6 @@
 
     <issue
         id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in return type Function2&lt;Boolean, Float, Unit> of &apos;getOnDrag$lint_module&apos;."
-        errorLine1="    internal val onDrag: (Boolean, Float) -> Unit = { isStart, offset ->"
-        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material3/Slider.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
         message="Use a functional interface instead of lambda syntax for lambdas with primitive values in variable &apos;isValidDistance&apos; with type Function1&lt;? super Float, ? extends Boolean>."
         errorLine1="        fun Float.isValidDistance(): Boolean {"
         errorLine2="        ^">
@@ -678,15 +606,6 @@
 
     <issue
         id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method swipeAnchors has parameter &apos;calculateAnchor&apos; with type Function2&lt;? super T, ? super IntSize, Float>."
-        errorLine1="    calculateAnchor: (value: T, layoutSize: IntSize) -> Float?,"
-        errorLine2="                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material3/SwipeableV2.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
         message="Use a functional interface instead of lambda syntax for lambdas with primitive values in variable &apos;valueToOffset&apos; with type Function1&lt;? super Boolean, ? extends Float>."
         errorLine1="    val valueToOffset = remember&lt;(Boolean) -> Float>(minBound, maxBound) {"
         errorLine2="    ^">
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ColorScheme.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ColorScheme.kt
index 1684398..38811eb 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ColorScheme.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ColorScheme.kt
@@ -20,13 +20,9 @@
 import androidx.compose.material3.tokens.ColorLightTokens
 import androidx.compose.material3.tokens.ColorSchemeKeyTokens
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.ReadOnlyComposable
-import androidx.compose.runtime.Stable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
 import androidx.compose.runtime.staticCompositionLocalOf
-import androidx.compose.runtime.structuralEqualityPolicy
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.compositeOver
 import androidx.compose.ui.graphics.takeOrElse
@@ -112,44 +108,44 @@
  * [surfaceContainerLow]. Use this role for content which requires less emphasis than
  * [surfaceContainerLow].
  */
-@Stable
+@Immutable
 class ColorScheme(
-    primary: Color,
-    onPrimary: Color,
-    primaryContainer: Color,
-    onPrimaryContainer: Color,
-    inversePrimary: Color,
-    secondary: Color,
-    onSecondary: Color,
-    secondaryContainer: Color,
-    onSecondaryContainer: Color,
-    tertiary: Color,
-    onTertiary: Color,
-    tertiaryContainer: Color,
-    onTertiaryContainer: Color,
-    background: Color,
-    onBackground: Color,
-    surface: Color,
-    onSurface: Color,
-    surfaceVariant: Color,
-    onSurfaceVariant: Color,
-    surfaceTint: Color,
-    inverseSurface: Color,
-    inverseOnSurface: Color,
-    error: Color,
-    onError: Color,
-    errorContainer: Color,
-    onErrorContainer: Color,
-    outline: Color,
-    outlineVariant: Color,
-    scrim: Color,
-    surfaceBright: Color,
-    surfaceDim: Color,
-    surfaceContainer: Color,
-    surfaceContainerHigh: Color,
-    surfaceContainerHighest: Color,
-    surfaceContainerLow: Color,
-    surfaceContainerLowest: Color,
+    val primary: Color,
+    val onPrimary: Color,
+    val primaryContainer: Color,
+    val onPrimaryContainer: Color,
+    val inversePrimary: Color,
+    val secondary: Color,
+    val onSecondary: Color,
+    val secondaryContainer: Color,
+    val onSecondaryContainer: Color,
+    val tertiary: Color,
+    val onTertiary: Color,
+    val tertiaryContainer: Color,
+    val onTertiaryContainer: Color,
+    val background: Color,
+    val onBackground: Color,
+    val surface: Color,
+    val onSurface: Color,
+    val surfaceVariant: Color,
+    val onSurfaceVariant: Color,
+    val surfaceTint: Color,
+    val inverseSurface: Color,
+    val inverseOnSurface: Color,
+    val error: Color,
+    val onError: Color,
+    val errorContainer: Color,
+    val onErrorContainer: Color,
+    val outline: Color,
+    val outlineVariant: Color,
+    val scrim: Color,
+    val surfaceBright: Color,
+    val surfaceDim: Color,
+    val surfaceContainer: Color,
+    val surfaceContainerHigh: Color,
+    val surfaceContainerHighest: Color,
+    val surfaceContainerLow: Color,
+    val surfaceContainerLowest: Color,
 ) {
     constructor(
         primary: Color,
@@ -219,79 +215,6 @@
         surfaceContainerLow = Color.Unspecified,
         surfaceContainerLowest = Color.Unspecified,
     )
-    var primary by mutableStateOf(primary, structuralEqualityPolicy())
-        internal set
-    var onPrimary by mutableStateOf(onPrimary, structuralEqualityPolicy())
-        internal set
-    var primaryContainer by mutableStateOf(primaryContainer, structuralEqualityPolicy())
-        internal set
-    var onPrimaryContainer by mutableStateOf(onPrimaryContainer, structuralEqualityPolicy())
-        internal set
-    var inversePrimary by mutableStateOf(inversePrimary, structuralEqualityPolicy())
-        internal set
-    var secondary by mutableStateOf(secondary, structuralEqualityPolicy())
-        internal set
-    var onSecondary by mutableStateOf(onSecondary, structuralEqualityPolicy())
-        internal set
-    var secondaryContainer by mutableStateOf(secondaryContainer, structuralEqualityPolicy())
-        internal set
-    var onSecondaryContainer by mutableStateOf(onSecondaryContainer, structuralEqualityPolicy())
-        internal set
-    var tertiary by mutableStateOf(tertiary, structuralEqualityPolicy())
-        internal set
-    var onTertiary by mutableStateOf(onTertiary, structuralEqualityPolicy())
-        internal set
-    var tertiaryContainer by mutableStateOf(tertiaryContainer, structuralEqualityPolicy())
-        internal set
-    var onTertiaryContainer by mutableStateOf(onTertiaryContainer, structuralEqualityPolicy())
-        internal set
-    var background by mutableStateOf(background, structuralEqualityPolicy())
-        internal set
-    var onBackground by mutableStateOf(onBackground, structuralEqualityPolicy())
-        internal set
-    var surface by mutableStateOf(surface, structuralEqualityPolicy())
-        internal set
-    var onSurface by mutableStateOf(onSurface, structuralEqualityPolicy())
-        internal set
-    var surfaceVariant by mutableStateOf(surfaceVariant, structuralEqualityPolicy())
-        internal set
-    var onSurfaceVariant by mutableStateOf(onSurfaceVariant, structuralEqualityPolicy())
-        internal set
-    var surfaceTint by mutableStateOf(surfaceTint, structuralEqualityPolicy())
-        internal set
-    var inverseSurface by mutableStateOf(inverseSurface, structuralEqualityPolicy())
-        internal set
-    var inverseOnSurface by mutableStateOf(inverseOnSurface, structuralEqualityPolicy())
-        internal set
-    var error by mutableStateOf(error, structuralEqualityPolicy())
-        internal set
-    var onError by mutableStateOf(onError, structuralEqualityPolicy())
-        internal set
-    var errorContainer by mutableStateOf(errorContainer, structuralEqualityPolicy())
-        internal set
-    var onErrorContainer by mutableStateOf(onErrorContainer, structuralEqualityPolicy())
-        internal set
-    var outline by mutableStateOf(outline, structuralEqualityPolicy())
-        internal set
-    var outlineVariant by mutableStateOf(outlineVariant, structuralEqualityPolicy())
-        internal set
-    var scrim by mutableStateOf(scrim, structuralEqualityPolicy())
-        internal set
-    var surfaceBright by mutableStateOf(surfaceBright, structuralEqualityPolicy())
-        internal set
-    var surfaceDim by mutableStateOf(surfaceDim, structuralEqualityPolicy())
-        internal set
-    var surfaceContainer by mutableStateOf(surfaceContainer, structuralEqualityPolicy())
-        internal set
-    var surfaceContainerHigh by mutableStateOf(surfaceContainerHigh, structuralEqualityPolicy())
-        internal set
-    var surfaceContainerHighest by mutableStateOf(
-        surfaceContainerHighest, structuralEqualityPolicy())
-        internal set
-    var surfaceContainerLow by mutableStateOf(surfaceContainerLow, structuralEqualityPolicy())
-        internal set
-    var surfaceContainerLowest by mutableStateOf(surfaceContainerLowest, structuralEqualityPolicy())
-        internal set
 
     /** Returns a copy of this ColorScheme, optionally overriding some of the values. */
     fun copy(
@@ -882,59 +805,6 @@
 }
 
 /**
- * Updates the internal values of a given [ColorScheme] with values from the [other]
- * [ColorScheme].
- * This allows efficiently updating a subset of [ColorScheme], without recomposing every
- * composable that consumes values from [LocalColorScheme].
- *
- * Because [ColorScheme] is very wide-reaching, and used by many expensive composables in the
- * hierarchy, providing a new value to [LocalColorScheme] causes every composable consuming
- * [LocalColorScheme] to recompose, which is prohibitively expensive in cases such as animating one
- * color in the theme. Instead, [ColorScheme] is internally backed by [mutableStateOf], and this
- * function mutates the internal state of [this] to match values in [other]. This means that any
- * changes will mutate the internal state of [this], and only cause composables that are reading the
- * specific changed value to recompose.
- */
-internal fun ColorScheme.updateColorSchemeFrom(other: ColorScheme) {
-    primary = other.primary
-    onPrimary = other.onPrimary
-    primaryContainer = other.primaryContainer
-    onPrimaryContainer = other.onPrimaryContainer
-    inversePrimary = other.inversePrimary
-    secondary = other.secondary
-    onSecondary = other.onSecondary
-    secondaryContainer = other.secondaryContainer
-    onSecondaryContainer = other.onSecondaryContainer
-    tertiary = other.tertiary
-    onTertiary = other.onTertiary
-    tertiaryContainer = other.tertiaryContainer
-    onTertiaryContainer = other.onTertiaryContainer
-    background = other.background
-    onBackground = other.onBackground
-    surface = other.surface
-    onSurface = other.onSurface
-    surfaceVariant = other.surfaceVariant
-    onSurfaceVariant = other.onSurfaceVariant
-    surfaceTint = other.surfaceTint
-    inverseSurface = other.inverseSurface
-    inverseOnSurface = other.inverseOnSurface
-    error = other.error
-    onError = other.onError
-    errorContainer = other.errorContainer
-    onErrorContainer = other.onErrorContainer
-    outline = other.outline
-    outlineVariant = other.outlineVariant
-    scrim = other.scrim
-    surfaceBright = other.surfaceBright
-    surfaceDim = other.surfaceDim
-    surfaceContainer = other.surfaceContainer
-    surfaceContainerHigh = other.surfaceContainerHigh
-    surfaceContainerHighest = other.surfaceContainerHighest
-    surfaceContainerLow = other.surfaceContainerLow
-    surfaceContainerLowest = other.surfaceContainerLowest
-}
-
-/**
  * Helper function for component color tokens. Here is an example on how to use component color
  * tokens:
  * ``MaterialTheme.colorScheme.fromToken(ExtendedFabBranded.BrandedContainerColor)``
@@ -984,9 +854,8 @@
 /**
  * CompositionLocal used to pass [ColorScheme] down the tree.
  *
- * Setting the value here is typically done as part of [MaterialTheme], which will automatically
- * handle efficiently updating any changed colors without causing unnecessary recompositions, using
- * [ColorScheme.updateColorSchemeFrom]. To retrieve the current value of this CompositionLocal, use
+ * Setting the value here is typically done as part of [MaterialTheme].
+ * To retrieve the current value of this CompositionLocal, use
  * [MaterialTheme.colorScheme].
  */
 internal val LocalColorScheme = staticCompositionLocalOf { lightColorScheme() }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MaterialTheme.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MaterialTheme.kt
index 3120ad86..546efcd 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MaterialTheme.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MaterialTheme.kt
@@ -61,17 +61,10 @@
     typography: Typography = MaterialTheme.typography,
     content: @Composable () -> Unit
 ) {
-    val rememberedColorScheme = remember {
-        // Explicitly creating a new object here so we don't mutate the initial [colorScheme]
-        // provided, and overwrite the values set in it.
-        colorScheme.copy()
-    }.apply {
-        updateColorSchemeFrom(colorScheme)
-    }
     val rippleIndication = rememberRipple()
-    val selectionColors = rememberTextSelectionColors(rememberedColorScheme)
+    val selectionColors = rememberTextSelectionColors(colorScheme)
     CompositionLocalProvider(
-        LocalColorScheme provides rememberedColorScheme,
+        LocalColorScheme provides colorScheme,
         LocalIndication provides rippleIndication,
         LocalRippleTheme provides MaterialRippleTheme,
         LocalShapes provides shapes,
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 21e04e7..22cfbbf 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
@@ -43,8 +43,6 @@
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
 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.composed
 import androidx.compose.ui.graphics.Color
@@ -422,11 +420,10 @@
     initialIsVisible: Boolean = false,
     isPersistent: Boolean = false,
     mutatorMutex: MutatorMutex = BasicTooltipDefaults.GlobalMutatorMutex
-): TooltipState {
-    return rememberSaveable(
+): TooltipState =
+    remember(
         isPersistent,
-        mutatorMutex,
-        saver = TooltipStateImpl.Saver
+        mutatorMutex
     ) {
         TooltipStateImpl(
             initialIsVisible = initialIsVisible,
@@ -434,7 +431,6 @@
             mutatorMutex = mutatorMutex
         )
     }
-}
 
 /**
  * Constructor extension function for [TooltipState]
@@ -528,29 +524,6 @@
     override fun onDispose() {
         job?.cancel()
     }
-
-    companion object {
-        /**
-         * The default [Saver] implementation for [TooltipStateImpl].
-         */
-        val Saver = Saver<TooltipStateImpl, Any>(
-            save = {
-                listOf(
-                    it.isVisible,
-                    it.isPersistent,
-                    it.mutatorMutex
-                )
-            },
-            restore = {
-                val (isVisible, isPersistent, mutatorMutex) = it as List<*>
-                TooltipStateImpl(
-                    initialIsVisible = isVisible as Boolean,
-                    isPersistent = isPersistent as Boolean,
-                    mutatorMutex = mutatorMutex as MutatorMutex,
-                )
-            }
-        )
-    }
 }
 
 /**
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/RuntimeIssueRegistry.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/RuntimeIssueRegistry.kt
index 1c78dec..1ead6c8 100644
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/RuntimeIssueRegistry.kt
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/RuntimeIssueRegistry.kt
@@ -31,8 +31,7 @@
     override val minApi = CURRENT_API
     override val issues get() = listOf(
         AutoboxingStateValuePropertyDetector.AutoboxingStateValueProperty,
-        // Disabled due to b/298019499.
-        // AutoboxingStateCreationDetector.AutoboxingStateCreation,
+        AutoboxingStateCreationDetector.AutoboxingStateCreation,
         ComposableCoroutineCreationDetector.CoroutineCreationDuringComposition,
         ComposableFlowOperatorDetector.FlowOperatorInvokedInComposition,
         ComposableLambdaParameterDetector.ComposableLambdaParameterNaming,
diff --git a/compose/runtime/runtime/api/current.ignore b/compose/runtime/runtime/api/current.ignore
deleted file mode 100644
index 1a1e78f..0000000
--- a/compose/runtime/runtime/api/current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-RemovedClass: androidx.compose.runtime.PrimitiveSnapshotStateKt:
-    Removed class androidx.compose.runtime.PrimitiveSnapshotStateKt
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index 9dfa745..90d3092 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -445,6 +445,12 @@
     property public final boolean isPaused;
   }
 
+  public final class PrimitiveSnapshotStateKt {
+    method public static inline operator float getValue(androidx.compose.runtime.FloatState, Object? thisObj, kotlin.reflect.KProperty<?> property);
+    method @androidx.compose.runtime.snapshots.StateFactoryMarker public static androidx.compose.runtime.MutableFloatState mutableFloatStateOf(float value);
+    method public static inline operator void setValue(androidx.compose.runtime.MutableFloatState, Object? thisObj, kotlin.reflect.KProperty<?> property, float value);
+  }
+
   public interface ProduceStateScope<T> extends androidx.compose.runtime.MutableState<T> kotlinx.coroutines.CoroutineScope {
     method public suspend Object? awaitDispose(kotlin.jvm.functions.Function0<kotlin.Unit> onDispose, kotlin.coroutines.Continuation<?>);
   }
@@ -549,12 +555,6 @@
     method public static inline operator void setValue(androidx.compose.runtime.MutableDoubleState, Object? thisObj, kotlin.reflect.KProperty<?> property, double value);
   }
 
-  public final class SnapshotFloatStateKt {
-    method public static inline operator float getValue(androidx.compose.runtime.FloatState, Object? thisObj, kotlin.reflect.KProperty<?> property);
-    method @androidx.compose.runtime.snapshots.StateFactoryMarker public static androidx.compose.runtime.MutableFloatState mutableFloatStateOf(float value);
-    method public static inline operator void setValue(androidx.compose.runtime.MutableFloatState, Object? thisObj, kotlin.reflect.KProperty<?> property, float value);
-  }
-
   public final class SnapshotIntStateKt {
     method public static inline operator int getValue(androidx.compose.runtime.IntState, Object? thisObj, kotlin.reflect.KProperty<?> property);
     method @androidx.compose.runtime.snapshots.StateFactoryMarker public static androidx.compose.runtime.MutableIntState mutableIntStateOf(int value);
diff --git a/compose/runtime/runtime/api/restricted_current.ignore b/compose/runtime/runtime/api/restricted_current.ignore
deleted file mode 100644
index 1a1e78f..0000000
--- a/compose/runtime/runtime/api/restricted_current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-RemovedClass: androidx.compose.runtime.PrimitiveSnapshotStateKt:
-    Removed class androidx.compose.runtime.PrimitiveSnapshotStateKt
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index 7e80ac0..69c5a8e 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -477,6 +477,12 @@
     property public final boolean isPaused;
   }
 
+  public final class PrimitiveSnapshotStateKt {
+    method public static inline operator float getValue(androidx.compose.runtime.FloatState, Object? thisObj, kotlin.reflect.KProperty<?> property);
+    method @androidx.compose.runtime.snapshots.StateFactoryMarker public static androidx.compose.runtime.MutableFloatState mutableFloatStateOf(float value);
+    method public static inline operator void setValue(androidx.compose.runtime.MutableFloatState, Object? thisObj, kotlin.reflect.KProperty<?> property, float value);
+  }
+
   public interface ProduceStateScope<T> extends androidx.compose.runtime.MutableState<T> kotlinx.coroutines.CoroutineScope {
     method public suspend Object? awaitDispose(kotlin.jvm.functions.Function0<kotlin.Unit> onDispose, kotlin.coroutines.Continuation<?>);
   }
@@ -585,12 +591,6 @@
     method public static inline operator void setValue(androidx.compose.runtime.MutableDoubleState, Object? thisObj, kotlin.reflect.KProperty<?> property, double value);
   }
 
-  public final class SnapshotFloatStateKt {
-    method public static inline operator float getValue(androidx.compose.runtime.FloatState, Object? thisObj, kotlin.reflect.KProperty<?> property);
-    method @androidx.compose.runtime.snapshots.StateFactoryMarker public static androidx.compose.runtime.MutableFloatState mutableFloatStateOf(float value);
-    method public static inline operator void setValue(androidx.compose.runtime.MutableFloatState, Object? thisObj, kotlin.reflect.KProperty<?> property, float value);
-  }
-
   public final class SnapshotIntStateKt {
     method public static inline operator int getValue(androidx.compose.runtime.IntState, Object? thisObj, kotlin.reflect.KProperty<?> property);
     method @androidx.compose.runtime.snapshots.StateFactoryMarker public static androidx.compose.runtime.MutableIntState mutableIntStateOf(int value);
diff --git a/compose/runtime/runtime/src/androidMain/baseline-prof.txt b/compose/runtime/runtime/src/androidMain/baseline-prof.txt
index ea25040..d98cdda 100644
--- a/compose/runtime/runtime/src/androidMain/baseline-prof.txt
+++ b/compose/runtime/runtime/src/androidMain/baseline-prof.txt
@@ -42,6 +42,7 @@
 HSPLandroidx/compose/runtime/ParcelableSnapshotMutableState**->**(**)**
 HSPLandroidx/compose/runtime/PausableMonotonicFrameClock;->**(**)**
 HSPLandroidx/compose/runtime/Pending**->**(**)**
+HSPLandroidx/compose/runtime/PrimitiveSnapshotStateKt**->**(**)**
 HSPLandroidx/compose/runtime/ProvidableCompositionLocal;->**(**)**
 HSPLandroidx/compose/runtime/ProvidedValue;->**(**)**
 HSPLandroidx/compose/runtime/RecomposeScopeImpl;->**(**)**
@@ -54,7 +55,6 @@
 HSPLandroidx/compose/runtime/SlotWriter;->**(**)**
 HSPLandroidx/compose/runtime/SnapshotMutableStateImpl**->**(**)**
 HSPLandroidx/compose/runtime/SnapshotDoubleStateKt**->**(**)**
-HSPLandroidx/compose/runtime/SnapshotFloatStateKt**->**(**)**
 HSPLandroidx/compose/runtime/SnapshotIntStateKt**->**(**)**
 HSPLandroidx/compose/runtime/SnapshotLongStateKt**->**(**)**
 HSPLandroidx/compose/runtime/SnapshotStateKt**->**(**)**
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotFloatState.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotFloatState.kt
index 1490f39..f083f5e 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotFloatState.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotFloatState.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-@file:JvmName("SnapshotFloatStateKt")
+@file:JvmName("PrimitiveSnapshotStateKt")
 @file:JvmMultifileClass
 package androidx.compose.runtime
 
diff --git a/compose/ui/ui-graphics/api/current.txt b/compose/ui/ui-graphics/api/current.txt
index 66bd67f..b4e7d7e 100644
--- a/compose/ui/ui-graphics/api/current.txt
+++ b/compose/ui/ui-graphics/api/current.txt
@@ -1367,6 +1367,7 @@
     method public androidx.compose.ui.graphics.vector.PathBuilder reflectiveQuadToRelative(float dx1, float dy1);
     method public androidx.compose.ui.graphics.vector.PathBuilder verticalLineTo(float y);
     method public androidx.compose.ui.graphics.vector.PathBuilder verticalLineToRelative(float dy);
+    property public final java.util.List<androidx.compose.ui.graphics.vector.PathNode> nodes;
   }
 
   @androidx.compose.runtime.Immutable public abstract sealed class PathNode {
diff --git a/compose/ui/ui-graphics/api/restricted_current.txt b/compose/ui/ui-graphics/api/restricted_current.txt
index ecfd67e..c8d7b1d 100644
--- a/compose/ui/ui-graphics/api/restricted_current.txt
+++ b/compose/ui/ui-graphics/api/restricted_current.txt
@@ -1426,6 +1426,7 @@
     method public androidx.compose.ui.graphics.vector.PathBuilder reflectiveQuadToRelative(float dx1, float dy1);
     method public androidx.compose.ui.graphics.vector.PathBuilder verticalLineTo(float y);
     method public androidx.compose.ui.graphics.vector.PathBuilder verticalLineToRelative(float dy);
+    property public final java.util.List<androidx.compose.ui.graphics.vector.PathNode> nodes;
   }
 
   @androidx.compose.runtime.Immutable public abstract sealed class PathNode {
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathBuilder.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathBuilder.kt
index 8754a23..49d5949 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathBuilder.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathBuilder.kt
@@ -17,28 +17,56 @@
 package androidx.compose.ui.graphics.vector
 
 class PathBuilder {
+    // 88% of Material icons use 32 or fewer path nodes
+    private val _nodes = ArrayList<PathNode>(32)
 
-    private val nodes = mutableListOf<PathNode>()
+    val nodes: List<PathNode>
+        get() = _nodes
 
-    fun getNodes(): List<PathNode> = nodes
+    fun close(): PathBuilder {
+        _nodes.add(PathNode.Close)
+        return this
+    }
 
-    fun close(): PathBuilder = addNode(PathNode.Close)
+    fun moveTo(x: Float, y: Float): PathBuilder {
+        _nodes.add(PathNode.MoveTo(x, y))
+        return this
+    }
 
-    fun moveTo(x: Float, y: Float) = addNode(PathNode.MoveTo(x, y))
+    fun moveToRelative(dx: Float, dy: Float): PathBuilder {
+        _nodes.add(PathNode.RelativeMoveTo(dx, dy))
+        return this
+    }
 
-    fun moveToRelative(dx: Float, dy: Float) = addNode(PathNode.RelativeMoveTo(dx, dy))
+    fun lineTo(x: Float, y: Float): PathBuilder {
+        _nodes.add(PathNode.LineTo(x, y))
+        return this
+    }
 
-    fun lineTo(x: Float, y: Float) = addNode(PathNode.LineTo(x, y))
+    fun lineToRelative(dx: Float, dy: Float): PathBuilder {
+        _nodes.add(PathNode.RelativeLineTo(dx, dy))
+        return this
+    }
 
-    fun lineToRelative(dx: Float, dy: Float) = addNode(PathNode.RelativeLineTo(dx, dy))
+    fun horizontalLineTo(x: Float): PathBuilder {
+        _nodes.add(PathNode.HorizontalTo(x))
+        return this
+    }
 
-    fun horizontalLineTo(x: Float) = addNode(PathNode.HorizontalTo(x))
+    fun horizontalLineToRelative(dx: Float): PathBuilder {
+        _nodes.add(PathNode.RelativeHorizontalTo(dx))
+        return this
+    }
 
-    fun horizontalLineToRelative(dx: Float) = addNode(PathNode.RelativeHorizontalTo(dx))
+    fun verticalLineTo(y: Float): PathBuilder {
+        _nodes.add(PathNode.VerticalTo(y))
+        return this
+    }
 
-    fun verticalLineTo(y: Float) = addNode(PathNode.VerticalTo(y))
-
-    fun verticalLineToRelative(dy: Float) = addNode(PathNode.RelativeVerticalTo(dy))
+    fun verticalLineToRelative(dy: Float): PathBuilder {
+        _nodes.add(PathNode.RelativeVerticalTo(dy))
+        return this
+    }
 
     fun curveTo(
         x1: Float,
@@ -47,7 +75,10 @@
         y2: Float,
         x3: Float,
         y3: Float
-    ) = addNode(PathNode.CurveTo(x1, y1, x2, y2, x3, y3))
+    ): PathBuilder {
+        _nodes.add(PathNode.CurveTo(x1, y1, x2, y2, x3, y3))
+        return this
+    }
 
     fun curveToRelative(
         dx1: Float,
@@ -56,25 +87,40 @@
         dy2: Float,
         dx3: Float,
         dy3: Float
-    ) = addNode(PathNode.RelativeCurveTo(dx1, dy1, dx2, dy2, dx3, dy3))
+    ): PathBuilder {
+        _nodes.add(PathNode.RelativeCurveTo(dx1, dy1, dx2, dy2, dx3, dy3))
+        return this
+    }
 
-    fun reflectiveCurveTo(x1: Float, y1: Float, x2: Float, y2: Float) =
-        addNode(PathNode.ReflectiveCurveTo(x1, y1, x2, y2))
+    fun reflectiveCurveTo(x1: Float, y1: Float, x2: Float, y2: Float): PathBuilder {
+        _nodes.add(PathNode.ReflectiveCurveTo(x1, y1, x2, y2))
+        return this
+    }
 
-    fun reflectiveCurveToRelative(dx1: Float, dy1: Float, dx2: Float, dy2: Float) =
-        addNode(PathNode.RelativeReflectiveCurveTo(dx1, dy1, dx2, dy2))
+    fun reflectiveCurveToRelative(dx1: Float, dy1: Float, dx2: Float, dy2: Float): PathBuilder {
+        _nodes.add(PathNode.RelativeReflectiveCurveTo(dx1, dy1, dx2, dy2))
+        return this
+    }
 
-    fun quadTo(x1: Float, y1: Float, x2: Float, y2: Float) =
-        addNode(PathNode.QuadTo(x1, y1, x2, y2))
+    fun quadTo(x1: Float, y1: Float, x2: Float, y2: Float): PathBuilder {
+        _nodes.add(PathNode.QuadTo(x1, y1, x2, y2))
+        return this
+    }
 
-    fun quadToRelative(dx1: Float, dy1: Float, dx2: Float, dy2: Float) =
-        addNode(PathNode.RelativeQuadTo(dx1, dy1, dx2, dy2))
+    fun quadToRelative(dx1: Float, dy1: Float, dx2: Float, dy2: Float): PathBuilder {
+        _nodes.add(PathNode.RelativeQuadTo(dx1, dy1, dx2, dy2))
+        return this
+    }
 
-    fun reflectiveQuadTo(x1: Float, y1: Float) =
-        addNode(PathNode.ReflectiveQuadTo(x1, y1))
+    fun reflectiveQuadTo(x1: Float, y1: Float): PathBuilder {
+        _nodes.add(PathNode.ReflectiveQuadTo(x1, y1))
+        return this
+    }
 
-    fun reflectiveQuadToRelative(dx1: Float, dy1: Float) =
-        addNode(PathNode.RelativeReflectiveQuadTo(dx1, dy1))
+    fun reflectiveQuadToRelative(dx1: Float, dy1: Float): PathBuilder {
+        _nodes.add(PathNode.RelativeReflectiveQuadTo(dx1, dy1))
+        return this
+    }
 
     fun arcTo(
         horizontalEllipseRadius: Float,
@@ -84,17 +130,20 @@
         isPositiveArc: Boolean,
         x1: Float,
         y1: Float
-    ) = addNode(
-        PathNode.ArcTo(
-            horizontalEllipseRadius,
-            verticalEllipseRadius,
-            theta,
-            isMoreThanHalf,
-            isPositiveArc,
-            x1,
-            y1
+    ): PathBuilder {
+        _nodes.add(
+            PathNode.ArcTo(
+                horizontalEllipseRadius,
+                verticalEllipseRadius,
+                theta,
+                isMoreThanHalf,
+                isPositiveArc,
+                x1,
+                y1
+            )
         )
-    )
+        return this
+    }
 
     fun arcToRelative(
         a: Float,
@@ -104,10 +153,8 @@
         isPositiveArc: Boolean,
         dx1: Float,
         dy1: Float
-    ) = addNode(PathNode.RelativeArcTo(a, b, theta, isMoreThanHalf, isPositiveArc, dx1, dy1))
-
-    private fun addNode(node: PathNode): PathBuilder {
-        nodes.add(node)
+    ): PathBuilder {
+        _nodes.add(PathNode.RelativeArcTo(a, b, theta, isMoreThanHalf, isPositiveArc, dx1, dy1))
         return this
     }
 }
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathNode.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathNode.kt
index 536eb5b..41f582a 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathNode.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathNode.kt
@@ -147,7 +147,7 @@
  * If the key is unknown then [IllegalArgumentException] is thrown
  * @throws IllegalArgumentException
  */
-internal fun Char.addPathNodes(nodes: MutableList<PathNode>, args: FloatArray, count: Int) {
+internal fun Char.addPathNodes(nodes: ArrayList<PathNode>, args: FloatArray, count: Int) {
     when (this) {
         RelativeCloseKey, CloseKey -> nodes.add(PathNode.Close)
 
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathParser.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathParser.kt
index b4e0ed9..405910e 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathParser.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathParser.kt
@@ -49,7 +49,7 @@
 internal val EmptyArray = FloatArray(0)
 
 class PathParser {
-    private val nodes = mutableListOf<PathNode>()
+    private val nodes = ArrayList<PathNode>()
 
     private val floatResult = FloatResult()
     private var nodeData = FloatArray(64)
@@ -139,7 +139,7 @@
         return this
     }
 
-    fun toNodes() = nodes
+    fun toNodes(): List<PathNode> = nodes
 
     fun toPath(target: Path = Path()) = nodes.toPath(target)
 
diff --git a/compose/ui/ui-text/lint-baseline.xml b/compose/ui/ui-text/lint-baseline.xml
index f49a0ce..a0b00ff 100644
--- a/compose/ui/ui-text/lint-baseline.xml
+++ b/compose/ui/ui-text/lint-baseline.xml
@@ -1,31 +1,85 @@
 <?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.3.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
 
     <issue
-        id="BanInlineOptIn"
-        message="Inline functions cannot opt into experimental APIs."
-        errorLine1="internal inline fun &lt;T> List&lt;T>.fastForEach(action: (T) -> Unit) {"
-        errorLine2="                                ~~~~~~~~~~~">
+        id="NewApi"
+        message="Call requires API level 29 (current min is 21): `android.graphics.Paint#getBlendMode`"
+        errorLine1="        assertThat(paragraph.textPaint.blendMode).isEqualTo(BlendMode.SrcOver)"
+        errorLine2="                                       ~~~~~~~~~">
         <location
-            file="../../../text/text/src/main/java/androidx/compose/ui/text/android/TempListUtils.kt"/>
+            file="src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/AndroidParagraphTest.kt"/>
     </issue>
 
     <issue
-        id="BanInlineOptIn"
-        message="Inline functions cannot opt into experimental APIs."
-        errorLine1="internal inline fun &lt;T, R, C : MutableCollection&lt;in R>> List&lt;T>.fastMapTo("
-        errorLine2="                                                                ~~~~~~~~~">
+        id="NewApi"
+        message="Cast from `BlendMode` to `Comparable` requires API level 29 (current min is 21)"
+        errorLine1="        assertThat(paragraph.textPaint.blendMode).isEqualTo(BlendMode.SrcOver)"
+        errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
-            file="../../../text/text/src/main/java/androidx/compose/ui/text/android/TempListUtils.kt"/>
+            file="src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/AndroidParagraphTest.kt"/>
     </issue>
 
     <issue
-        id="BanInlineOptIn"
-        message="Inline functions cannot opt into experimental APIs."
-        errorLine1="internal inline fun &lt;T, R> List&lt;T>.fastZipWithNext(transform: (T, T) -> R): List&lt;R> {"
-        errorLine2="                                   ~~~~~~~~~~~~~~~">
+        id="NewApi"
+        message="Call requires API level 29 (current min is 21): `android.graphics.Paint#getBlendMode`"
+        errorLine1="        assertThat(paragraph.textPaint.blendMode).isEqualTo(BlendMode.SrcOver)"
+        errorLine2="                                       ~~~~~~~~~">
         <location
-            file="../../../text/text/src/main/java/androidx/compose/ui/text/android/TempListUtils.kt"/>
+            file="src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/AndroidParagraphTest.kt"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Cast from `BlendMode` to `Comparable` requires API level 29 (current min is 21)"
+        errorLine1="        assertThat(paragraph.textPaint.blendMode).isEqualTo(BlendMode.SrcOver)"
+        errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/AndroidParagraphTest.kt"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 29 (current min is 21): `android.graphics.Paint#getBlendMode`"
+        errorLine1="        assertThat(textPaint.blendMode).isEqualTo(BlendMode.SrcOver)"
+        errorLine2="                             ~~~~~~~~~">
+        <location
+            file="src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/platform/AndroidTextPaintTest.kt"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Cast from `BlendMode` to `Comparable` requires API level 29 (current min is 21)"
+        errorLine1="        assertThat(textPaint.blendMode).isEqualTo(BlendMode.SrcOver)"
+        errorLine2="                   ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/platform/AndroidTextPaintTest.kt"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 29 (current min is 21): `android.graphics.Paint#setBlendMode`"
+        errorLine1="        textPaint.blendMode = BlendMode.DstOver"
+        errorLine2="                  ~~~~~~~~~">
+        <location
+            file="src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/platform/AndroidTextPaintTest.kt"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 29 (current min is 21): `android.graphics.Paint#getBlendMode`"
+        errorLine1="        assertThat(textPaint.blendMode).isEqualTo(BlendMode.DstOver)"
+        errorLine2="                             ~~~~~~~~~">
+        <location
+            file="src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/platform/AndroidTextPaintTest.kt"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Cast from `BlendMode` to `Comparable` requires API level 29 (current min is 21)"
+        errorLine1="        assertThat(textPaint.blendMode).isEqualTo(BlendMode.DstOver)"
+        errorLine2="                   ~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/platform/AndroidTextPaintTest.kt"/>
     </issue>
 
     <issue
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
index e96dac5..00182f8 100644
--- 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
@@ -52,6 +52,13 @@
             RecreateVectorPainterTestCase()
         }, assertOneRecomposition = false)
     }
+
+    @Test
+    fun renderVectorWithDifferentSizes() {
+        benchmarkRule.toggleStateBenchmarkDraw({
+            ResizeVectorPainter()
+        }, assertOneRecomposition = false)
+    }
 }
 
 private class RecreateVectorPainterTestCase : ComposeTestCase, ToggleableTestCase {
@@ -80,3 +87,39 @@
         }
     }
 }
+
+private class ResizeVectorPainter : 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(100.dp),
+                    alpha = alpha
+                )
+            }
+
+            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/lint-baseline.xml b/compose/ui/ui/lint-baseline.xml
index a1d8f22..8dadbf5 100644
--- a/compose/ui/ui/lint-baseline.xml
+++ b/compose/ui/ui/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="cli" dependencies="false" name="AGP (8.2.0-alpha15)" variant="all" version="8.2.0-alpha15">
+<issues format="6" by="lint 8.3.0-alpha02" type="baseline" client="cli" dependencies="false" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
 
     <issue
         id="BanHideTag"
@@ -130,15 +130,6 @@
     <issue
         id="PrimitiveInLambda"
         message="Use a functional interface instead of lambda syntax for lambdas with primitive values in variable &apos;scrollAction&apos; with type AccessibilityAction&lt;Function2&lt;? super Float, ? super Float, ? extends Boolean>>."
-        errorLine1="                val scrollAction ="
-        errorLine2="                ^">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in variable &apos;scrollAction&apos; with type AccessibilityAction&lt;Function2&lt;? super Float, ? super Float, ? extends Boolean>>."
         errorLine1="                var scrollAction = scrollableAncestor?.config?.getOrNull(SemanticsActions.ScrollBy)"
         errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt
index 0e0a75b..6edf095 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/vector/VectorTest.kt
@@ -36,6 +36,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 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
@@ -175,6 +176,50 @@
         }
     }
 
+    @Test
+    fun testVectorSkipsRecompositionOnNoChange() {
+        val state = mutableIntStateOf(0)
+        var composeCount = 0
+        var vectorComposeCount = 0
+
+        val composeVector: @Composable @VectorComposable (Float, Float) -> Unit = {
+                viewportWidth, viewportHeight ->
+
+            vectorComposeCount++
+            Path(
+                fill = SolidColor(Color.Blue),
+                pathData = PathData {
+                    lineTo(viewportWidth, 0f)
+                    lineTo(viewportWidth, viewportHeight)
+                    lineTo(0f, viewportHeight)
+                    close()
+                }
+            )
+        }
+
+        rule.setContent {
+            composeCount++
+            // Arbitrary read to force composition here and verify the subcomposition below skips
+            state.value
+            val vectorPainter = rememberVectorPainter(
+                defaultWidth = 10.dp,
+                defaultHeight = 10.dp,
+                autoMirror = false,
+                content = composeVector
+            )
+            Image(
+                vectorPainter,
+                null,
+                modifier = Modifier.size(20.dp)
+            )
+        }
+
+        state.value = 1
+        rule.waitForIdle()
+        assertEquals(2, composeCount) // Arbitrary state read should compose twice
+        assertEquals(1, vectorComposeCount) // Vector is identical so should compose once
+    }
+
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     @Test
     fun testVectorInvalidation() {
@@ -1063,6 +1108,7 @@
         var vectorInCache = false
         rule.setContent {
             val theme = LocalContext.current.theme
+            val density = LocalDensity.current
             val imageVectorCache = LocalImageVectorCache.current
             imageVectorCache.clear()
             Image(
@@ -1070,8 +1116,22 @@
                 contentDescription = null
             )
 
-            vectorInCache =
-                imageVectorCache[ImageVectorCache.Key(theme, R.drawable.ic_triangle)] != null
+            val key = ImageVectorCache.Key(theme, R.drawable.ic_triangle, density)
+            vectorInCache = imageVectorCache[key] != null
+        }
+
+        assertTrue(vectorInCache)
+    }
+
+    @Test
+    fun testVectorPainterCacheHit() {
+        var vectorInCache = false
+        rule.setContent {
+            // obtaining the same painter resource should return the same instance root
+            // GroupComponent
+            val painter1 = painterResource(R.drawable.ic_triangle) as VectorPainter
+            val painter2 = painterResource(R.drawable.ic_triangle) as VectorPainter
+            vectorInCache = painter1.vector.root === painter2.vector.root
         }
 
         assertTrue(vectorInCache)
@@ -1083,8 +1143,10 @@
         var application: Application? = null
         var theme: Resources.Theme? = null
         var vectorCache: ImageVectorCache? = null
+        var density: Density? = null
         rule.setContent {
             application = LocalContext.current.applicationContext as Application
+            density = LocalDensity.current
             theme = LocalContext.current.theme
             val imageVectorCache = LocalImageVectorCache.current
             imageVectorCache.clear()
@@ -1093,8 +1155,8 @@
                 contentDescription = null
             )
 
-            vectorInCache =
-                imageVectorCache[ImageVectorCache.Key(theme!!, R.drawable.ic_triangle)] != null
+            val key = ImageVectorCache.Key(theme!!, R.drawable.ic_triangle, density!!)
+            vectorInCache = imageVectorCache[key] != null
 
             vectorCache = imageVectorCache
         }
@@ -1102,7 +1164,7 @@
         application?.onTrimMemory(0)
 
         val cacheCleared = vectorCache?.let {
-            it[ImageVectorCache.Key(theme!!, R.drawable.ic_triangle)] == null
+            it[ImageVectorCache.Key(theme!!, R.drawable.ic_triangle, density!!)] == null
         } ?: false
 
         assertTrue("Vector was not inserted in cache after initial creation", vectorInCache)
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/PointerIconTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/PointerIconTest.kt
index cacc19c..508c2fc 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/PointerIconTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/input/pointer/PointerIconTest.kt
@@ -108,6 +108,53 @@
      *    ⤷ Child Box (custom icon = [PointerIcon.Text], overrideDescendants = FALSE)
      *
      *  Expected Output:
+     *  Because we don't move the cursor, the icon will be the default [PointerIcon.Default]. We
+     *  also want to check that when using a .pointerHoverIcon modifier with a composable,
+     *  composition only happens once (per composable).
+     */
+    @Test
+    fun parentChildFullOverlap_noOverrideDescendants_checkNumberOfCompositions() {
+
+        var numberOfCompositions = 0
+
+        rule.setContent {
+            CompositionLocalProvider(LocalPointerIconService provides iconService) {
+                Box(
+                    modifier = Modifier
+                        .requiredSize(200.dp)
+                        .border(BorderStroke(2.dp, SolidColor(Color.Red)))
+                        .testTag(parentIconTag)
+                        .pointerHoverIcon(desiredParentIcon, overrideDescendants = false)
+                ) {
+
+                    numberOfCompositions++
+
+                    Box(
+                        Modifier
+                            .requiredSize(200.dp)
+                            .border(BorderStroke(2.dp, SolidColor(Color.Black)))
+                            .testTag(childIconTag)
+                            .pointerHoverIcon(desiredChildIcon, overrideDescendants = false)
+                    ) {
+                        numberOfCompositions++
+                    }
+                }
+            }
+        }
+        // Verify initial state of pointer icon
+        rule.runOnIdle {
+            assertThat(iconService.getIcon()).isEqualTo(desiredDefaultIcon)
+            assertThat(numberOfCompositions).isEqualTo(2)
+        }
+    }
+
+    /**
+     * Setup:
+     * The hierarchy for this test is setup as:
+     *  Parent Box (custom icon = [PointerIcon.Crosshair], overrideDescendants = FALSE)
+     *    ⤷ Child Box (custom icon = [PointerIcon.Text], overrideDescendants = FALSE)
+     *
+     *  Expected Output:
      *  Child Box’s [PointerIcon.Text] wins for the entire Box area because it’s lower in
      *  the hierarchy than Parent Box. If the Parent Box's overrideDescendants = false, the Child
      *  Box takes priority.
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/res/PainterResources.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/res/PainterResources.android.kt
index 32dcbe67..3f330b4 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/res/PainterResources.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/res/PainterResources.android.kt
@@ -26,12 +26,15 @@
 import androidx.compose.ui.graphics.nativeCanvas
 import androidx.compose.ui.graphics.painter.BitmapPainter
 import androidx.compose.ui.graphics.painter.Painter
-import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.graphics.vector.GroupComponent
 import androidx.compose.ui.graphics.vector.VectorPainter
 import androidx.compose.ui.graphics.vector.compat.seekToStartTag
-import androidx.compose.ui.graphics.vector.rememberVectorPainter
+import androidx.compose.ui.graphics.vector.createGroupComponent
+import androidx.compose.ui.graphics.vector.createVectorPainterFromImageVector
 import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalImageVectorCache
+import androidx.compose.ui.res.ImageVectorCache.ImageVectorEntry
 
 /**
  * Create a [Painter] from an Android resource id. This can load either an instance of
@@ -62,8 +65,7 @@
     val path = value.string
     // Assume .xml suffix implies loading a VectorDrawable resource
     return if (path?.endsWith(".xml") == true) {
-        val imageVector = loadVectorResource(context.theme, res, id, value.changingConfigurations)
-        rememberVectorPainter(imageVector)
+        obtainVectorPainter(context.theme, res, id, value.changingConfigurations)
     } else {
         // Otherwise load the bitmap resource
         val imageBitmap = remember(path, id, context.theme) {
@@ -74,29 +76,42 @@
 }
 
 /**
- * Helper method to validate that the xml resource is a vector drawable then load
- * the ImageVector. Because this throws exceptions we cannot have this implementation as part of
- * the composable implementation it is invoked in.
+ * Helper method to load the previously cached VectorPainter instance if it exists, otherwise
+ * this parses the xml into an ImageVector and creates a new VectorPainter inserting it into the
+ * cache for reuse
  */
 @Composable
-private fun loadVectorResource(
+private fun obtainVectorPainter(
     theme: Resources.Theme,
     res: Resources,
     id: Int,
     changingConfigurations: Int
-): ImageVector {
+): VectorPainter {
     val imageVectorCache = LocalImageVectorCache.current
-    val key = ImageVectorCache.Key(theme, id)
-    var imageVectorEntry = imageVectorCache[key]
-    if (imageVectorEntry == null) {
+    val density = LocalDensity.current
+    val key = remember(theme, id, density) {
+        ImageVectorCache.Key(theme, id, density)
+    }
+    val imageVectorEntry = imageVectorCache[key]
+    var imageVector = imageVectorEntry?.imageVector
+    if (imageVector == null) {
         @Suppress("ResourceType") val parser = res.getXml(id)
         if (parser.seekToStartTag().name != "vector") {
             throw IllegalArgumentException(errorMessage)
         }
-        imageVectorEntry = loadVectorResourceInner(theme, res, parser, changingConfigurations)
-        imageVectorCache[key] = imageVectorEntry
+        imageVector = loadVectorResourceInner(theme, res, parser)
     }
-    return imageVectorEntry.imageVector
+
+    var rootGroup = imageVectorEntry?.rootGroup
+    if (rootGroup == null) {
+        rootGroup = GroupComponent().apply {
+            createGroupComponent(imageVector.root)
+        }
+        imageVectorCache[key] = ImageVectorEntry(imageVector, changingConfigurations, rootGroup)
+    }
+    return remember(key) {
+        createVectorPainterFromImageVector(density, imageVector, rootGroup)
+    }
 }
 
 /**
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/res/VectorResources.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/res/VectorResources.android.kt
index eeb02ee..9c5cfdd 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/res/VectorResources.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/res/VectorResources.android.kt
@@ -24,6 +24,7 @@
 import androidx.annotation.DrawableRes
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
+import androidx.compose.ui.graphics.vector.GroupComponent
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.graphics.vector.compat.AndroidVectorParser
 import androidx.compose.ui.graphics.vector.compat.createVectorImageBuilder
@@ -31,6 +32,9 @@
 import androidx.compose.ui.graphics.vector.compat.parseCurrentVectorNode
 import androidx.compose.ui.graphics.vector.compat.seekToStartTag
 import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalImageVectorCache
+import androidx.compose.ui.unit.Density
 import java.lang.ref.WeakReference
 import org.xmlpull.v1.XmlPullParserException
 
@@ -46,13 +50,26 @@
  */
 @Composable
 fun ImageVector.Companion.vectorResource(@DrawableRes id: Int): ImageVector {
+    val imageCache = LocalImageVectorCache.current
     val context = LocalContext.current
+    val density = LocalDensity.current
     val res = resources()
     val theme = context.theme
-
-    return remember(id, res, theme, res.configuration) {
-        vectorResource(theme, res, id)
+    val key = remember(theme, id, density) {
+        ImageVectorCache.Key(theme, id, density)
     }
+    var imageVector = imageCache[key]?.imageVector
+    if (imageVector == null) {
+        val value = remember { TypedValue() }
+        res.getValue(id, value, true)
+        imageVector = vectorResource(theme, res, id)
+        imageCache[key] = ImageVectorCache.ImageVectorEntry(
+            imageVector,
+            value.changingConfigurations,
+            null
+        )
+    }
+    return imageVector
 }
 
 @Throws(XmlPullParserException::class)
@@ -61,15 +78,11 @@
     res: Resources,
     resId: Int
 ): ImageVector {
-    val value = TypedValue()
-    res.getValue(resId, value, true)
-
     return loadVectorResourceInner(
         theme,
         res,
         res.getXml(resId).apply { seekToStartTag() },
-        value.changingConfigurations
-    ).imageVector
+    )
 }
 
 /**
@@ -81,9 +94,8 @@
 internal fun loadVectorResourceInner(
     theme: Resources.Theme? = null,
     res: Resources,
-    parser: XmlResourceParser,
-    changingConfigurations: Int
-): ImageVectorCache.ImageVectorEntry {
+    parser: XmlResourceParser
+): ImageVector {
     val attrs = Xml.asAttributeSet(parser)
     val resourceParser = AndroidVectorParser(parser)
     val builder = resourceParser.createVectorImageBuilder(res, theme, attrs)
@@ -99,7 +111,7 @@
         )
         parser.next()
     }
-    return ImageVectorCache.ImageVectorEntry(builder.build(), changingConfigurations)
+    return builder.build()
 }
 
 /**
@@ -113,7 +125,8 @@
      */
     data class Key(
         val theme: Resources.Theme,
-        val id: Int
+        val id: Int,
+        val density: Density
     )
 
     /**
@@ -123,7 +136,8 @@
      */
     data class ImageVectorEntry(
         val imageVector: ImageVector,
-        val configFlags: Int
+        val configFlags: Int,
+        val rootGroup: GroupComponent?,
     )
 
     private val map = HashMap<Key, WeakReference<ImageVectorEntry>>()
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/Vector.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/Vector.kt
index 972e0ae..fd2d8fb 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/Vector.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/Vector.kt
@@ -19,6 +19,7 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.geometry.Size.Companion.Unspecified
 import androidx.compose.ui.graphics.BlendMode
@@ -36,6 +37,7 @@
 import androidx.compose.ui.graphics.StrokeJoin
 import androidx.compose.ui.graphics.drawscope.DrawScope
 import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.graphics.drawscope.scale
 import androidx.compose.ui.graphics.drawscope.withTransform
 import androidx.compose.ui.graphics.isSpecified
 import androidx.compose.ui.graphics.isUnspecified
@@ -69,7 +71,7 @@
 
 inline fun PathData(block: PathBuilder.() -> Unit) = with(PathBuilder()) {
     block()
-    getNodes()
+    nodes
 }
 
 fun addPathNodes(pathStr: String?) = if (pathStr == null) {
@@ -92,20 +94,15 @@
     abstract fun DrawScope.draw()
 }
 
-internal class VectorComponent : VNode() {
-    val root = GroupComponent().apply {
-        pivotX = 0.0f
-        pivotY = 0.0f
-        invalidateListener = {
+internal class VectorComponent(val root: GroupComponent) : VNode() {
+
+    init {
+        root.invalidateListener = {
             doInvalidate()
         }
     }
 
-    var name: String
-        get() = root.name
-        set(value) {
-            root.name = value
-        }
+    var name: String = DefaultGroupName
 
     private fun doInvalidate() {
         isDirty = true
@@ -131,11 +128,18 @@
 
     private var previousDrawSize = Unspecified
 
+    private var rootScaleX = 1f
+    private var rootScaleY = 1f
+
     /**
      * Cached lambda used to avoid allocating the lambda on each draw invocation
      */
     private val drawVectorBlock: DrawScope.() -> Unit = {
-        with(root) { draw() }
+        with(root) {
+            scale(rootScaleX, rootScaleY, pivot = Offset.Zero) {
+                draw()
+            }
+        }
     }
 
     fun DrawScope.draw(alpha: Float, colorFilter: ColorFilter?) {
@@ -155,8 +159,8 @@
             } else {
                 null
             }
-            root.scaleX = size.width / viewportSize.width
-            root.scaleY = size.height / viewportSize.height
+            rootScaleX = size.width / viewportSize.width
+            rootScaleY = size.height / viewportSize.height
             cacheDrawScope.drawCachedImage(
                 targetImageConfig,
                 IntSize(ceil(size.width).toInt(), ceil(size.height).toInt()),
@@ -266,29 +270,23 @@
 
     var trimPathStart = DefaultTrimPathStart
         set(value) {
-            if (field != value) {
-                field = value
-                isTrimPathDirty = true
-                invalidate()
-            }
+            field = value
+            isTrimPathDirty = true
+            invalidate()
         }
 
     var trimPathEnd = DefaultTrimPathEnd
         set(value) {
-            if (field != value) {
-                field = value
-                isTrimPathDirty = true
-                invalidate()
-            }
+            field = value
+            isTrimPathDirty = true
+            invalidate()
         }
 
     var trimPathOffset = DefaultTrimPathOffset
         set(value) {
-            if (field != value) {
-                field = value
-                isTrimPathDirty = true
-                invalidate()
-            }
+            field = value
+            isTrimPathDirty = true
+            invalidate()
         }
 
     private var isPathDirty = true
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorPainter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorPainter.kt
index 35225ac..fbc1930 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorPainter.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/VectorPainter.kt
@@ -19,8 +19,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.ComposableOpenTarget
 import androidx.compose.runtime.Composition
-import androidx.compose.runtime.CompositionContext
-import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableIntStateOf
 import androidx.compose.runtime.mutableStateOf
@@ -35,9 +33,11 @@
 import androidx.compose.ui.graphics.ImageBitmapConfig
 import androidx.compose.ui.graphics.drawscope.DrawScope
 import androidx.compose.ui.graphics.drawscope.scale
+import androidx.compose.ui.graphics.isSpecified
 import androidx.compose.ui.graphics.painter.Painter
 import androidx.compose.ui.internal.JvmDefaultWithCompatibility
 import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.LayoutDirection
 
@@ -127,26 +127,35 @@
     content: @Composable @VectorComposable (viewportWidth: Float, viewportHeight: Float) -> Unit
 ): VectorPainter {
     val density = LocalDensity.current
-    val widthPx = with(density) { defaultWidth.toPx() }
-    val heightPx = with(density) { defaultHeight.toPx() }
-
-    val vpWidth = if (viewportWidth.isNaN()) widthPx else viewportWidth
-    val vpHeight = if (viewportHeight.isNaN()) heightPx else viewportHeight
-
+    val defaultSize = density.obtainSizePx(defaultWidth, defaultHeight)
+    val viewport = obtainViewportSize(defaultSize, viewportWidth, viewportHeight)
     val intrinsicColorFilter = remember(tintColor, tintBlendMode) {
-        if (tintColor != Color.Unspecified) {
-            ColorFilter.tint(tintColor, tintBlendMode)
-        } else {
-            null
-        }
+        createColorFilter(tintColor, tintBlendMode)
     }
-
     return remember { VectorPainter() }.apply {
-        // These assignments are thread safe as parameters are backed by a mutableState object
-        size = Size(widthPx, heightPx)
-        this.autoMirror = autoMirror
-        this.intrinsicColorFilter = intrinsicColorFilter
-        RenderVector(name, vpWidth, vpHeight, content)
+        configureVectorPainter(
+            defaultSize = defaultSize,
+            viewportSize = viewport,
+            name = name,
+            intrinsicColorFilter = intrinsicColorFilter,
+            autoMirror = autoMirror
+        )
+        val compositionContext = rememberCompositionContext()
+        this.composition = remember(viewportWidth, viewportHeight, content) {
+            val curComp = this.composition
+            val next = if (curComp == null || curComp.isDisposed) {
+                Composition(
+                    VectorApplier(this.vector.root),
+                    compositionContext
+                )
+            } else {
+                curComp
+            }
+            next.setContent {
+                content(viewport.width, viewport.height)
+            }
+            next
+        }
     }
 }
 
@@ -175,7 +184,7 @@
  * This can be represented by either a [ImageVector] or a programmatic
  * composition of a vector
  */
-class VectorPainter internal constructor() : Painter() {
+class VectorPainter internal constructor(root: GroupComponent = GroupComponent()) : Painter() {
 
     internal var size by mutableStateOf(Size.Zero)
 
@@ -190,7 +199,19 @@
             vector.intrinsicColorFilter = value
         }
 
-    private val vector = VectorComponent().apply {
+    internal var viewportSize: Size
+        get() = vector.viewportSize
+        set(value) {
+            vector.viewportSize = value
+        }
+
+    internal var name: String
+        get() = vector.name
+        set(value) {
+            vector.name = value
+        }
+
+    internal val vector = VectorComponent(root).apply {
         invalidateCallback = {
             if (drawCount == invalidateCount) {
                 invalidateCount++
@@ -201,56 +222,11 @@
     internal val bitmapConfig: ImageBitmapConfig
         get() = vector.cacheBitmapConfig
 
-    private var composition: Composition? = null
-
-    @Suppress("PrimitiveInLambda")
-    private fun composeVector(
-        parent: CompositionContext,
-        composable: @Composable (viewportWidth: Float, viewportHeight: Float) -> Unit
-    ): Composition {
-        val existing = composition
-        val next = if (existing == null || existing.isDisposed) {
-            Composition(
-                VectorApplier(vector.root),
-                parent
-            )
-        } else {
-            existing
-        }
-        composition = next
-        next.setContent {
-            composable(vector.viewportSize.width, vector.viewportSize.height)
-        }
-        return next
-    }
+    internal var composition: Composition? = null
 
     // TODO replace with mutableStateOf(Unit, neverEqualPolicy()) after b/291647821 is addressed
     private var invalidateCount by mutableIntStateOf(0)
 
-    @Suppress("PrimitiveInLambda")
-    @Composable
-    internal fun RenderVector(
-        name: String,
-        viewportWidth: Float,
-        viewportHeight: Float,
-        content: @Composable (viewportWidth: Float, viewportHeight: Float) -> Unit
-    ) {
-        vector.apply {
-            this.name = name
-            this.viewportSize = Size(viewportWidth, viewportHeight)
-        }
-        val composition = composeVector(
-            rememberCompositionContext(),
-            content
-        )
-
-        DisposableEffect(composition) {
-            onDispose {
-                composition.dispose()
-            }
-        }
-    }
-
     private var currentAlpha: Float = 1.0f
     private var currentColorFilter: ColorFilter? = null
 
@@ -326,6 +302,117 @@
     }
 }
 
+private fun Density.obtainSizePx(defaultWidth: Dp, defaultHeight: Dp) =
+        Size(defaultWidth.toPx(), defaultHeight.toPx())
+
+/**
+ * Helper method to calculate the viewport size. If the viewport width/height are not specified
+ * this falls back on the default size provided
+ */
+private fun obtainViewportSize(
+    defaultSize: Size,
+    viewportWidth: Float,
+    viewportHeight: Float
+) = Size(
+        if (viewportWidth.isNaN()) defaultSize.width else viewportWidth,
+        if (viewportHeight.isNaN()) defaultSize.height else viewportHeight
+    )
+
+/**
+ * Helper method to conditionally create a ColorFilter to tint contents if [tintColor] is
+ * specified, that is [Color.isSpecified] returns true
+ */
+private fun createColorFilter(tintColor: Color, tintBlendMode: BlendMode): ColorFilter? =
+    if (tintColor.isSpecified) {
+        ColorFilter.tint(tintColor, tintBlendMode)
+    } else {
+        null
+    }
+
+/**
+ * Helper method to configure the properties of a VectorPainter that maybe re-used
+ */
+internal fun VectorPainter.configureVectorPainter(
+    defaultSize: Size,
+    viewportSize: Size,
+    name: String = RootGroupName,
+    intrinsicColorFilter: ColorFilter?,
+    autoMirror: Boolean = false,
+): VectorPainter = apply {
+        this.size = defaultSize
+        this.autoMirror = autoMirror
+        this.intrinsicColorFilter = intrinsicColorFilter
+        this.viewportSize = viewportSize
+        this.name = name
+    }
+
+/**
+ * Helper method to create a VectorPainter instance from an ImageVector
+ */
+internal fun createVectorPainterFromImageVector(
+    density: Density,
+    imageVector: ImageVector,
+    root: GroupComponent
+): VectorPainter {
+    val defaultSize = density.obtainSizePx(imageVector.defaultWidth, imageVector.defaultHeight)
+    val viewport = obtainViewportSize(
+        defaultSize,
+        imageVector.viewportWidth,
+        imageVector.viewportHeight
+    )
+    return VectorPainter(root).configureVectorPainter(
+        defaultSize = defaultSize,
+        viewportSize = viewport,
+        name = imageVector.name,
+        intrinsicColorFilter = createColorFilter(imageVector.tintColor, imageVector.tintBlendMode),
+        autoMirror = imageVector.autoMirror
+    )
+}
+
+/**
+ * statically create a a GroupComponent from the VectorGroup representation provided from
+ * an [ImageVector] instance
+ */
+internal fun GroupComponent.createGroupComponent(currentGroup: VectorGroup): GroupComponent {
+    for (index in 0 until currentGroup.size) {
+        val vectorNode = currentGroup[index]
+        if (vectorNode is VectorPath) {
+            val pathComponent = PathComponent().apply {
+                pathData = vectorNode.pathData
+                pathFillType = vectorNode.pathFillType
+                name = vectorNode.name
+                fill = vectorNode.fill
+                fillAlpha = vectorNode.fillAlpha
+                stroke = vectorNode.stroke
+                strokeAlpha = vectorNode.strokeAlpha
+                strokeLineWidth = vectorNode.strokeLineWidth
+                strokeLineCap = vectorNode.strokeLineCap
+                strokeLineJoin = vectorNode.strokeLineJoin
+                strokeLineMiter = vectorNode.strokeLineMiter
+                trimPathStart = vectorNode.trimPathStart
+                trimPathEnd = vectorNode.trimPathEnd
+                trimPathOffset = vectorNode.trimPathOffset
+            }
+            insertAt(index, pathComponent)
+        } else if (vectorNode is VectorGroup) {
+            val groupComponent = GroupComponent().apply {
+                name = vectorNode.name
+                rotation = vectorNode.rotation
+                scaleX = vectorNode.scaleX
+                scaleY = vectorNode.scaleY
+                translationX = vectorNode.translationX
+                translationY = vectorNode.translationY
+                pivotX = vectorNode.pivotX
+                pivotY = vectorNode.pivotY
+                clipPathData = vectorNode.clipPathData
+                createGroupComponent(vectorNode)
+            }
+            insertAt(index, groupComponent)
+        }
+    }
+    return this
+}
+
 /**
  * Recursively creates the vector graphic composition by traversing the tree structure.
  *
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeLayoutDelegate.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeLayoutDelegate.kt
index 2e4eb7a..7e16660 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeLayoutDelegate.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeLayoutDelegate.kt
@@ -244,6 +244,13 @@
     internal var lookaheadPassDelegate: LookaheadPassDelegate? = null
         private set
 
+    // Used by performMeasureBlock so that we don't have to allocate a lambda on every call
+    private var performMeasureConstraints = Constraints()
+
+    private val performMeasureBlock: () -> Unit = {
+        outerCoordinator.measure(performMeasureConstraints)
+    }
+
     fun onCoordinatesUsed() {
         val state = layoutNode.layoutState
         if (state == LayoutState.LayingOut || state == LayoutState.LookaheadLayingOut) {
@@ -349,6 +356,20 @@
         var layingOutChildren = false
             private set
 
+        private val layoutChildrenBlock: () -> Unit = {
+            clearPlaceOrder()
+            forEachChildAlignmentLinesOwner {
+                it.alignmentLines.usedDuringParentLayout = false
+            }
+            innerCoordinator.measureResult.placeChildren()
+
+            checkChildrenPlaceOrderForUpdates()
+            forEachChildAlignmentLinesOwner {
+                it.alignmentLines.previousUsedDuringParentLayout =
+                    it.alignmentLines.usedDuringParentLayout
+            }
+        }
+
         override fun layoutChildren() {
             layingOutChildren = true
             alignmentLines.recalculateQueryOwner()
@@ -370,20 +391,9 @@
                     val owner = requireOwner()
                     owner.snapshotObserver.observeLayoutSnapshotReads(
                         this,
-                        affectsLookahead = false
-                    ) {
-                        clearPlaceOrder()
-                        forEachChildAlignmentLinesOwner {
-                            it.alignmentLines.usedDuringParentLayout = false
-                        }
-                        innerCoordinator.measureResult.placeChildren()
-
-                        checkChildrenPlaceOrderForUpdates()
-                        forEachChildAlignmentLinesOwner {
-                            it.alignmentLines.previousUsedDuringParentLayout =
-                                it.alignmentLines.usedDuringParentLayout
-                        }
-                    }
+                        affectsLookahead = false,
+                        block = layoutChildrenBlock
+                    )
                 }
                 layoutState = oldLayoutState
 
@@ -464,6 +474,29 @@
 
         private var onNodePlacedCalled = false
 
+        // Used by placeOuterBlock to avoid allocating the lambda on every call
+        private var placeOuterCoordinatorLayerBlock: (GraphicsLayerScope.() -> Unit)? = null
+        private var placeOuterCoordinatorPosition = IntOffset.Zero
+        private var placeOuterCoordinatorZIndex = 0f
+
+        private val placeOuterCoordinatorBlock: () -> Unit = {
+            with(PlacementScope) {
+                val layerBlock = placeOuterCoordinatorLayerBlock
+                if (layerBlock == null) {
+                    outerCoordinator.place(
+                        placeOuterCoordinatorPosition,
+                        placeOuterCoordinatorZIndex
+                    )
+                } else {
+                    outerCoordinator.placeWithLayer(
+                        placeOuterCoordinatorPosition,
+                        placeOuterCoordinatorZIndex,
+                        layerBlock
+                    )
+                }
+            }
+        }
+
         /**
          * Invoked when the parent placed the node. It will trigger the layout.
          */
@@ -701,17 +734,13 @@
             } else {
                 alignmentLines.usedByModifierLayout = false
                 coordinatesAccessedDuringModifierPlacement = false
+                placeOuterCoordinatorLayerBlock = layerBlock
+                placeOuterCoordinatorPosition = position
+                placeOuterCoordinatorZIndex = zIndex
                 owner.snapshotObserver.observeLayoutModifierSnapshotReads(
-                    layoutNode, affectsLookahead = false
-                ) {
-                    with(PlacementScope) {
-                        if (layerBlock == null) {
-                            outerCoordinator.place(position, zIndex)
-                        } else {
-                            outerCoordinator.placeWithLayer(position, zIndex, layerBlock)
-                        }
-                    }
-                }
+                    layoutNode, affectsLookahead = false, block = placeOuterCoordinatorBlock
+                )
+                placeOuterCoordinatorLayerBlock = null
             }
 
             layoutState = LayoutState.Idle
@@ -1548,12 +1577,12 @@
         }
         layoutState = LayoutState.Measuring
         measurePending = false
+        performMeasureConstraints = constraints
         layoutNode.requireOwner().snapshotObserver.observeMeasureSnapshotReads(
             layoutNode,
-            affectsLookahead = false
-        ) {
-            outerCoordinator.measure(constraints)
-        }
+            affectsLookahead = false,
+            performMeasureBlock
+        )
         // The resulting layout state might be Ready. This can happen when the layout node's
         // own modifier is querying an alignment line during measurement, therefore we
         // need to also layout the layout node.
diff --git a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintLayout.kt b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintLayout.kt
index 337d63a..04f1c6d 100644
--- a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintLayout.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintLayout.kt
@@ -20,7 +20,7 @@
 import android.os.Handler
 import android.os.Looper
 import android.util.Log
-import androidx.collection.PairIntInt
+import androidx.collection.IntIntPair
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.tween
@@ -2019,7 +2019,7 @@
     private fun measureWidget(
         constraintWidget: ConstraintWidget,
         constraints: Constraints
-    ): PairIntInt {
+    ): IntIntPair {
         val measurable = constraintWidget.companionWidget
         val widgetId = constraintWidget.stringId
         return when {
@@ -2042,15 +2042,15 @@
                     heightMode,
                     constraints.maxHeight
                 )
-                PairIntInt(constraintWidget.measuredWidth, constraintWidget.measuredHeight)
+                IntIntPair(constraintWidget.measuredWidth, constraintWidget.measuredHeight)
             }
             measurable is Measurable -> {
                 val result = measurable.measure(constraints).also { placeables[measurable] = it }
-                PairIntInt(result.width, result.height)
+                IntIntPair(result.width, result.height)
             }
             else -> {
                 Log.w("CCL", "Nothing to measure for widget: $widgetId")
-                PairIntInt(0, 0)
+                IntIntPair(0, 0)
             }
         }
     }
diff --git a/core/core-telecom/api/current.txt b/core/core-telecom/api/current.txt
index 4bc7ae5..bc71ead 100644
--- a/core/core-telecom/api/current.txt
+++ b/core/core-telecom/api/current.txt
@@ -26,16 +26,29 @@
   public static final class CallAttributesCompat.Companion {
   }
 
+  public abstract sealed class CallControlResult {
+  }
+
+  public static final class CallControlResult.Error extends androidx.core.telecom.CallControlResult {
+    ctor public CallControlResult.Error(int errorCode);
+    method public int getErrorCode();
+    property public final int errorCode;
+  }
+
+  public static final class CallControlResult.Success extends androidx.core.telecom.CallControlResult {
+    ctor public CallControlResult.Success();
+  }
+
   public interface CallControlScope extends kotlinx.coroutines.CoroutineScope {
-    method public suspend Object? answer(int callType, kotlin.coroutines.Continuation<? super java.lang.Boolean>);
-    method public suspend Object? disconnect(android.telecom.DisconnectCause disconnectCause, kotlin.coroutines.Continuation<? super java.lang.Boolean>);
+    method public suspend Object? answer(int callType, kotlin.coroutines.Continuation<? super androidx.core.telecom.CallControlResult>);
+    method public suspend Object? disconnect(android.telecom.DisconnectCause disconnectCause, kotlin.coroutines.Continuation<? super androidx.core.telecom.CallControlResult>);
     method public kotlinx.coroutines.flow.Flow<java.util.List<androidx.core.telecom.CallEndpointCompat>> getAvailableEndpoints();
     method public android.os.ParcelUuid getCallId();
     method public kotlinx.coroutines.flow.Flow<androidx.core.telecom.CallEndpointCompat> getCurrentCallEndpoint();
     method public kotlinx.coroutines.flow.Flow<java.lang.Boolean> isMuted();
-    method public suspend Object? requestEndpointChange(androidx.core.telecom.CallEndpointCompat endpoint, kotlin.coroutines.Continuation<? super java.lang.Boolean>);
-    method public suspend Object? setActive(kotlin.coroutines.Continuation<? super java.lang.Boolean>);
-    method public suspend Object? setInactive(kotlin.coroutines.Continuation<? super java.lang.Boolean>);
+    method public suspend Object? requestEndpointChange(androidx.core.telecom.CallEndpointCompat endpoint, kotlin.coroutines.Continuation<? super androidx.core.telecom.CallControlResult>);
+    method public suspend Object? setActive(kotlin.coroutines.Continuation<? super androidx.core.telecom.CallControlResult>);
+    method public suspend Object? setInactive(kotlin.coroutines.Continuation<? super androidx.core.telecom.CallControlResult>);
     property public abstract kotlinx.coroutines.flow.Flow<java.util.List<androidx.core.telecom.CallEndpointCompat>> availableEndpoints;
     property public abstract kotlinx.coroutines.flow.Flow<androidx.core.telecom.CallEndpointCompat> currentCallEndpoint;
     property public abstract kotlinx.coroutines.flow.Flow<java.lang.Boolean> isMuted;
@@ -62,12 +75,18 @@
   }
 
   public final class CallException extends java.lang.RuntimeException {
-    ctor public CallException(optional int code, optional String? message);
+    ctor public CallException(optional int code);
     method public int getCode();
     property public final int code;
     field public static final androidx.core.telecom.CallException.Companion Companion;
-    field public static final int ERROR_BUILD_VERSION_CODE = 0; // 0x0
-    field public static final int ERROR_UNKNOWN_CODE = 1; // 0x1
+    field public static final int ERROR_BLUETOOTH_DEVICE_IS_NULL = 8; // 0x8
+    field public static final int ERROR_CALL_CANNOT_BE_SET_TO_ACTIVE = 4; // 0x4
+    field public static final int ERROR_CALL_DOES_NOT_SUPPORT_HOLD = 7; // 0x7
+    field public static final int ERROR_CALL_IS_NOT_BEING_TRACKED = 3; // 0x3
+    field public static final int ERROR_CALL_NOT_PERMITTED_AT_PRESENT_TIME = 5; // 0x5
+    field public static final int ERROR_CANNOT_HOLD_CURRENT_ACTIVE_CALL = 2; // 0x2
+    field public static final int ERROR_OPERATION_TIMED_OUT = 6; // 0x6
+    field public static final int ERROR_UNKNOWN = 1; // 0x1
   }
 
   public static final class CallException.Companion {
@@ -75,7 +94,7 @@
 
   @RequiresApi(android.os.Build.VERSION_CODES.O) public final class CallsManager {
     ctor public CallsManager(android.content.Context context);
-    method @RequiresPermission("android.permission.MANAGE_OWN_CALLS") public suspend Object? addCall(androidx.core.telecom.CallAttributesCompat callAttributes, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super kotlin.coroutines.Continuation<? super java.lang.Boolean>,?> onAnswer, kotlin.jvm.functions.Function2<? super android.telecom.DisconnectCause,? super kotlin.coroutines.Continuation<? super java.lang.Boolean>,?> onDisconnect, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super java.lang.Boolean>,?> onSetActive, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super java.lang.Boolean>,?> onSetInactive, kotlin.jvm.functions.Function1<? super androidx.core.telecom.CallControlScope,kotlin.Unit> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method @RequiresPermission("android.permission.MANAGE_OWN_CALLS") public suspend Object? addCall(androidx.core.telecom.CallAttributesCompat callAttributes, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onAnswer, kotlin.jvm.functions.Function2<? super android.telecom.DisconnectCause,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onDisconnect, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onSetActive, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onSetInactive, kotlin.jvm.functions.Function1<? super androidx.core.telecom.CallControlScope,kotlin.Unit> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method @RequiresPermission("android.permission.MANAGE_OWN_CALLS") public void registerAppWithTelecom(int capabilities);
     field public static final int CAPABILITY_BASELINE = 1; // 0x1
     field public static final int CAPABILITY_SUPPORTS_CALL_STREAMING = 4; // 0x4
diff --git a/core/core-telecom/api/restricted_current.txt b/core/core-telecom/api/restricted_current.txt
index 4bc7ae5..bc71ead 100644
--- a/core/core-telecom/api/restricted_current.txt
+++ b/core/core-telecom/api/restricted_current.txt
@@ -26,16 +26,29 @@
   public static final class CallAttributesCompat.Companion {
   }
 
+  public abstract sealed class CallControlResult {
+  }
+
+  public static final class CallControlResult.Error extends androidx.core.telecom.CallControlResult {
+    ctor public CallControlResult.Error(int errorCode);
+    method public int getErrorCode();
+    property public final int errorCode;
+  }
+
+  public static final class CallControlResult.Success extends androidx.core.telecom.CallControlResult {
+    ctor public CallControlResult.Success();
+  }
+
   public interface CallControlScope extends kotlinx.coroutines.CoroutineScope {
-    method public suspend Object? answer(int callType, kotlin.coroutines.Continuation<? super java.lang.Boolean>);
-    method public suspend Object? disconnect(android.telecom.DisconnectCause disconnectCause, kotlin.coroutines.Continuation<? super java.lang.Boolean>);
+    method public suspend Object? answer(int callType, kotlin.coroutines.Continuation<? super androidx.core.telecom.CallControlResult>);
+    method public suspend Object? disconnect(android.telecom.DisconnectCause disconnectCause, kotlin.coroutines.Continuation<? super androidx.core.telecom.CallControlResult>);
     method public kotlinx.coroutines.flow.Flow<java.util.List<androidx.core.telecom.CallEndpointCompat>> getAvailableEndpoints();
     method public android.os.ParcelUuid getCallId();
     method public kotlinx.coroutines.flow.Flow<androidx.core.telecom.CallEndpointCompat> getCurrentCallEndpoint();
     method public kotlinx.coroutines.flow.Flow<java.lang.Boolean> isMuted();
-    method public suspend Object? requestEndpointChange(androidx.core.telecom.CallEndpointCompat endpoint, kotlin.coroutines.Continuation<? super java.lang.Boolean>);
-    method public suspend Object? setActive(kotlin.coroutines.Continuation<? super java.lang.Boolean>);
-    method public suspend Object? setInactive(kotlin.coroutines.Continuation<? super java.lang.Boolean>);
+    method public suspend Object? requestEndpointChange(androidx.core.telecom.CallEndpointCompat endpoint, kotlin.coroutines.Continuation<? super androidx.core.telecom.CallControlResult>);
+    method public suspend Object? setActive(kotlin.coroutines.Continuation<? super androidx.core.telecom.CallControlResult>);
+    method public suspend Object? setInactive(kotlin.coroutines.Continuation<? super androidx.core.telecom.CallControlResult>);
     property public abstract kotlinx.coroutines.flow.Flow<java.util.List<androidx.core.telecom.CallEndpointCompat>> availableEndpoints;
     property public abstract kotlinx.coroutines.flow.Flow<androidx.core.telecom.CallEndpointCompat> currentCallEndpoint;
     property public abstract kotlinx.coroutines.flow.Flow<java.lang.Boolean> isMuted;
@@ -62,12 +75,18 @@
   }
 
   public final class CallException extends java.lang.RuntimeException {
-    ctor public CallException(optional int code, optional String? message);
+    ctor public CallException(optional int code);
     method public int getCode();
     property public final int code;
     field public static final androidx.core.telecom.CallException.Companion Companion;
-    field public static final int ERROR_BUILD_VERSION_CODE = 0; // 0x0
-    field public static final int ERROR_UNKNOWN_CODE = 1; // 0x1
+    field public static final int ERROR_BLUETOOTH_DEVICE_IS_NULL = 8; // 0x8
+    field public static final int ERROR_CALL_CANNOT_BE_SET_TO_ACTIVE = 4; // 0x4
+    field public static final int ERROR_CALL_DOES_NOT_SUPPORT_HOLD = 7; // 0x7
+    field public static final int ERROR_CALL_IS_NOT_BEING_TRACKED = 3; // 0x3
+    field public static final int ERROR_CALL_NOT_PERMITTED_AT_PRESENT_TIME = 5; // 0x5
+    field public static final int ERROR_CANNOT_HOLD_CURRENT_ACTIVE_CALL = 2; // 0x2
+    field public static final int ERROR_OPERATION_TIMED_OUT = 6; // 0x6
+    field public static final int ERROR_UNKNOWN = 1; // 0x1
   }
 
   public static final class CallException.Companion {
@@ -75,7 +94,7 @@
 
   @RequiresApi(android.os.Build.VERSION_CODES.O) public final class CallsManager {
     ctor public CallsManager(android.content.Context context);
-    method @RequiresPermission("android.permission.MANAGE_OWN_CALLS") public suspend Object? addCall(androidx.core.telecom.CallAttributesCompat callAttributes, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super kotlin.coroutines.Continuation<? super java.lang.Boolean>,?> onAnswer, kotlin.jvm.functions.Function2<? super android.telecom.DisconnectCause,? super kotlin.coroutines.Continuation<? super java.lang.Boolean>,?> onDisconnect, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super java.lang.Boolean>,?> onSetActive, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super java.lang.Boolean>,?> onSetInactive, kotlin.jvm.functions.Function1<? super androidx.core.telecom.CallControlScope,kotlin.Unit> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method @RequiresPermission("android.permission.MANAGE_OWN_CALLS") public suspend Object? addCall(androidx.core.telecom.CallAttributesCompat callAttributes, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onAnswer, kotlin.jvm.functions.Function2<? super android.telecom.DisconnectCause,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onDisconnect, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onSetActive, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onSetInactive, kotlin.jvm.functions.Function1<? super androidx.core.telecom.CallControlScope,kotlin.Unit> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method @RequiresPermission("android.permission.MANAGE_OWN_CALLS") public void registerAppWithTelecom(int capabilities);
     field public static final int CAPABILITY_BASELINE = 1; // 0x1
     field public static final int CAPABILITY_SUPPORTS_CALL_STREAMING = 4; // 0x4
diff --git a/core/core-telecom/integration-tests/testapp/src/main/java/androidx/core/telecom/test/CallListAdapter.kt b/core/core-telecom/integration-tests/testapp/src/main/java/androidx/core/telecom/test/CallListAdapter.kt
index a7598c2..bc49865 100644
--- a/core/core-telecom/integration-tests/testapp/src/main/java/androidx/core/telecom/test/CallListAdapter.kt
+++ b/core/core-telecom/integration-tests/testapp/src/main/java/androidx/core/telecom/test/CallListAdapter.kt
@@ -24,6 +24,7 @@
 import android.widget.Button
 import android.widget.TextView
 import androidx.annotation.RequiresApi
+import androidx.core.telecom.CallControlResult
 import androidx.recyclerview.widget.RecyclerView
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -32,8 +33,8 @@
 @RequiresApi(34)
 class CallListAdapter(private var mList: ArrayList<CallRow>?) :
     RecyclerView.Adapter<CallListAdapter.ViewHolder>() {
-
     var mCallIdToViewHolder: MutableMap<String, ViewHolder> = mutableMapOf()
+    private val CONTROL_ACTION_FAILED_MSG = "CurrentState=[FAILED-T]"
 
     class ViewHolder(ItemView: View) : RecyclerView.ViewHolder(ItemView) {
         // TextViews
@@ -78,16 +79,28 @@
 
             holder.activeButton.setOnClickListener {
                 CoroutineScope(Dispatchers.Main).launch {
-                    if (ItemsViewModel.callObject.mCallControl!!.setActive()) {
-                        holder.currentState.text = "CurrentState=[active]"
+                    when (ItemsViewModel.callObject.mCallControl!!.setActive()) {
+                        is CallControlResult.Success -> {
+                            holder.currentState.text = "CurrentState=[active]"
+                        }
+
+                        is CallControlResult.Error -> {
+                            holder.currentState.text = CONTROL_ACTION_FAILED_MSG
+                        }
                     }
                 }
             }
 
             holder.holdButton.setOnClickListener {
                 CoroutineScope(Dispatchers.Main).launch {
-                    if (ItemsViewModel.callObject.mCallControl!!.setInactive()) {
-                        holder.currentState.text = "CurrentState=[onHold]"
+                    when (ItemsViewModel.callObject.mCallControl!!.setInactive()) {
+                        is CallControlResult.Success -> {
+                            holder.currentState.text = "CurrentState=[onHold]"
+                        }
+
+                        is CallControlResult.Error -> {
+                            holder.currentState.text = CONTROL_ACTION_FAILED_MSG
+                        }
                     }
                 }
             }
@@ -121,11 +134,15 @@
                     val speakerEndpoint = ItemsViewModel.callObject
                         .getEndpointType(CallEndpoint.TYPE_SPEAKER)
                     if (speakerEndpoint != null) {
-                        val success = ItemsViewModel.callObject.mCallControl?.requestEndpointChange(
+                        when (ItemsViewModel.callObject.mCallControl!!.requestEndpointChange(
                             speakerEndpoint
-                        )
-                        if (success == true) {
-                            holder.currentEndpoint.text = "currentEndpoint=[speaker]"
+                        )) {
+                            is CallControlResult.Success -> {
+                                holder.currentState.text = "CurrentState=[speaker]"
+                            }
+                            is CallControlResult.Error -> {
+                                holder.currentState.text = CONTROL_ACTION_FAILED_MSG
+                            }
                         }
                     }
                 }
@@ -136,12 +153,17 @@
                     val bluetoothEndpoint = ItemsViewModel.callObject
                         .getEndpointType(CallEndpoint.TYPE_BLUETOOTH)
                     if (bluetoothEndpoint != null) {
-                        val success = ItemsViewModel.callObject.mCallControl?.requestEndpointChange(
+                        when (ItemsViewModel.callObject.mCallControl!!.requestEndpointChange(
                             bluetoothEndpoint
-                        )
-                        if (success == true) {
-                            holder.currentEndpoint.text =
-                                "currentEndpoint=[BT:${bluetoothEndpoint.name}]"
+                        )) {
+                            is CallControlResult.Success -> {
+                                holder.currentEndpoint.text =
+                                    "currentEndpoint=[BT:${bluetoothEndpoint.name}]"
+                            }
+                            is CallControlResult.Error -> {
+                                // e.g. tear down call and
+                                holder.currentState.text = CONTROL_ACTION_FAILED_MSG
+                            }
                         }
                     }
                 }
diff --git a/core/core-telecom/integration-tests/testapp/src/main/java/androidx/core/telecom/test/VoipCall.kt b/core/core-telecom/integration-tests/testapp/src/main/java/androidx/core/telecom/test/VoipCall.kt
index 5089cd9..1b18a37 100644
--- a/core/core-telecom/integration-tests/testapp/src/main/java/androidx/core/telecom/test/VoipCall.kt
+++ b/core/core-telecom/integration-tests/testapp/src/main/java/androidx/core/telecom/test/VoipCall.kt
@@ -33,28 +33,24 @@
     var mIsMuted = false
     var mTelecomCallId: String = ""
 
-    val mOnSetActiveLambda: suspend () -> Boolean = {
+    val mOnSetActiveLambda: suspend () -> Unit = {
         Log.i(TAG, "onSetActive: completing")
         mAdapter?.updateCallState(mTelecomCallId, "Active")
-        true
     }
 
-    val mOnSetInActiveLambda: suspend () -> Boolean = {
+    val mOnSetInActiveLambda: suspend () -> Unit = {
         Log.i(TAG, "onSetInactive: completing")
         mAdapter?.updateCallState(mTelecomCallId, "Inactive")
-        true
     }
 
-    val mOnAnswerLambda: suspend (type: Int) -> Boolean = {
+    val mOnAnswerLambda: suspend (type: Int) -> Unit = {
         Log.i(TAG, "onAnswer: callType=[$it]")
         mAdapter?.updateCallState(mTelecomCallId, "Answered")
-        true
     }
 
-    val mOnDisconnectLambda: suspend (cause: DisconnectCause) -> Boolean = {
+    val mOnDisconnectLambda: suspend (cause: DisconnectCause) -> Unit = {
         Log.i(TAG, "onDisconnect: disconnectCause=[$it]")
         mAdapter?.updateCallState(mTelecomCallId, "Disconnected")
-        true
     }
 
     fun setCallControl(callControl: CallControlScope) {
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/BasicCallControlCallbacksTest.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/BasicCallControlCallbacksTest.kt
index 717b07b9..2410360 100644
--- a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/BasicCallControlCallbacksTest.kt
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/BasicCallControlCallbacksTest.kt
@@ -21,6 +21,7 @@
 import android.telecom.CallAttributes
 import android.telecom.DisconnectCause
 import androidx.annotation.RequiresApi
+import androidx.core.telecom.CallControlResult
 import androidx.core.telecom.internal.utils.Utils
 import androidx.core.telecom.test.utils.BaseTelecomTest
 import androidx.core.telecom.test.utils.TestUtils
@@ -31,6 +32,7 @@
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
 import org.junit.After
+import org.junit.Assert
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertTrue
@@ -87,7 +89,7 @@
     @Test(timeout = 10000)
     fun testRejectCallControlCallbackAnswerCall() {
         setUpV2Test()
-        verifyRejectAnswerCall(Call.STATE_ACTIVE)
+        verifyRejectAnswerCall()
     }
 
     /**
@@ -207,7 +209,7 @@
     @Test(timeout = 10000)
     fun testRejectCallControlCallbackAnswerCall_BackwardsCompat() {
         setUpBackwardsCompatTest()
-        verifyRejectAnswerCall(Call.STATE_DISCONNECTED)
+        verifyRejectAnswerCall()
     }
 
     /**
@@ -317,14 +319,19 @@
                 TestUtils.mOnAnswerLambda,
                 TestUtils.mOnDisconnectLambda,
                 TestUtils.mOnSetActiveLambda,
-                TestUtils.mOnSetInActiveLambda) {
+                TestUtils.mOnSetInActiveLambda
+            ) {
                 launch {
                     val call = TestUtils.waitOnInCallServiceToReachXCalls(1)
                     assertNotNull("The returned Call object is <NULL>", call)
                     call!!.answer(0) // API under test
                     TestUtils.waitOnCallState(call, Call.STATE_ACTIVE)
-                    // Always send the disconnect signal if possible:
-                    assertTrue(disconnect(DisconnectCause(DisconnectCause.LOCAL)))
+                    // Terminal the call and ensure the call is cleaned up. Otherwise, fail
+                    // the test.
+                    Assert.assertEquals(
+                        CallControlResult.Success(),
+                        disconnect(DisconnectCause(DisconnectCause.LOCAL))
+                    )
                 }
             }
         }
@@ -341,15 +348,20 @@
                 TestUtils.mOnAnswerLambda,
                 TestUtils.mOnDisconnectLambda,
                 TestUtils.mOnSetActiveLambda,
-                TestUtils.mOnSetInActiveLambda) {
+                TestUtils.mOnSetInActiveLambda
+            ) {
                 launch {
                     val call = TestUtils.waitOnInCallServiceToReachXCalls(1)
                     assertNotNull("The returned Call object is <NULL>", call)
                     // Disconnect the call and ensure the disconnect callback is received:
                     call!!.disconnect()
                     TestUtils.waitOnCallState(call, Call.STATE_DISCONNECTED)
-                    // always send the disconnect signal if possible
-                    disconnect(DisconnectCause(DisconnectCause.LOCAL))
+                    // Terminal the call and ensure the call is cleaned up. Otherwise, fail
+                    // the test.
+                    Assert.assertEquals(
+                        CallControlResult.Success(),
+                        disconnect(DisconnectCause(DisconnectCause.LOCAL))
+                    )
                 }
             }
         }
@@ -366,18 +378,23 @@
                 TestUtils.mOnAnswerLambda,
                 TestUtils.mOnDisconnectLambda,
                 TestUtils.mOnSetActiveLambda,
-                TestUtils.mOnSetInActiveLambda) {
+                TestUtils.mOnSetInActiveLambda
+            ) {
                 launch {
                     val call = TestUtils.waitOnInCallServiceToReachXCalls(1)
                     assertNotNull("The returned Call object is <NULL>", call)
-                    assertTrue(setActive())
+                    Assert.assertEquals(CallControlResult.Success(), setActive())
                     // Wait for the call to be set to ACTIVE:
                     TestUtils.waitOnCallState(call!!, Call.STATE_ACTIVE)
                     // Place the call on hold and ensure the onSetInactive callback is received:
                     call.hold()
                     TestUtils.waitOnCallState(call, Call.STATE_HOLDING)
-                    // Always send the disconnect signal if possible:
-                    assertTrue(disconnect(DisconnectCause(DisconnectCause.LOCAL)))
+                    // Terminal the call and ensure the call is cleaned up. Otherwise, fail
+                    // the test.
+                    Assert.assertEquals(
+                        CallControlResult.Success(),
+                        disconnect(DisconnectCause(DisconnectCause.LOCAL))
+                    )
                 }
             }
         }
@@ -394,21 +411,27 @@
                 TestUtils.mOnAnswerLambda,
                 TestUtils.mOnDisconnectLambda,
                 TestUtils.mOnSetActiveLambda,
-                TestUtils.mOnSetInActiveLambda) {
+                TestUtils.mOnSetInActiveLambda
+            ) {
                 launch {
                     val call = TestUtils.waitOnInCallServiceToReachXCalls(1)
                     assertNotNull("The returned Call object is <NULL>", call)
-                    assertTrue(setActive())
+                    Assert.assertEquals(CallControlResult.Success(), setActive())
                     // Wait for the call to be set to ACTIVE:
                     TestUtils.waitOnCallState(call!!, Call.STATE_ACTIVE)
-                    assertTrue(setInactive())
+                    Assert.assertEquals(CallControlResult.Success(), setInactive())
                     // Wait for the call to be set to HOLDING (aka inactive):
                     TestUtils.waitOnCallState(call, Call.STATE_HOLDING)
                     // Request to un-hold the call and ensure the onSetActive callback is received:
                     call.unhold()
                     TestUtils.waitOnCallState(call, Call.STATE_ACTIVE)
-                    // Always send the disconnect signal if possible:
-                    assertTrue(disconnect(DisconnectCause(DisconnectCause.LOCAL)))
+                    delay(100)
+                    // Terminal the call and ensure the call is cleaned up. Otherwise, fail
+                    // the test.
+                    Assert.assertEquals(
+                        CallControlResult.Success(),
+                        disconnect(DisconnectCause(DisconnectCause.LOCAL))
+                    )
                 }
             }
         }
@@ -417,59 +440,63 @@
     }
 
     @Suppress("deprecation")
-    private fun verifyRejectAnswerCall(callState: Int) {
+    private fun verifyRejectAnswerCall() {
         assertFalse(TestUtils.mOnAnswerCallbackCalled)
-        runBlocking {
-            mCallsManager.addCall(
-                TestUtils.INCOMING_CALL_ATTRIBUTES,
-                TestUtils.mOnAnswerLambda,
-                TestUtils.mOnDisconnectLambda,
-                TestUtils.mOnSetActiveLambda,
-                TestUtils.mOnSetInActiveLambda) {
-                // Note that this is reset in BaseTelecomTest in setUp/destroy
-                TestUtils.mCompleteOnAnswer = false
-                launch {
-                    val call = TestUtils.waitOnInCallServiceToReachXCalls(1)
-                    assertNotNull("The returned Call object is <NULL>", call)
-                    call!!.answer(0) // API under test
-                    // Ensure that call has moved to the expected state
-                    TestUtils.waitOnCallState(call, callState)
-                    // Ensure that call is automatically disconnected.
-                    TestUtils.waitOnCallState(call, Call.STATE_DISCONNECTED)
-                    // always send the disconnect signal if possible
-                    disconnect(DisconnectCause(DisconnectCause.LOCAL))
+        var catchBlockWasEntered = false
+
+        try {
+            runBlocking {
+                mCallsManager.addCall(
+                    TestUtils.INCOMING_CALL_ATTRIBUTES,
+                    TestUtils.mOnAnswerLambda,
+                    TestUtils.mOnDisconnectLambda,
+                    TestUtils.mOnSetActiveLambda,
+                    TestUtils.mOnSetInActiveLambda
+                ) {
+                    // Note that this is reset in BaseTelecomTest in setUp/destroy
+                    TestUtils.mCompleteOnAnswer = false
+                    launch {
+                        val call = TestUtils.waitOnInCallServiceToReachXCalls(1)
+                        assertNotNull("The returned Call object is <NULL>", call)
+                        call!!.answer(0) // API under test
+                    }
                 }
             }
+        } catch (e: Exception) {
+            catchBlockWasEntered = true
         }
         // Assert that the correct callback was invoked
+        assertTrue(catchBlockWasEntered)
         assertTrue(TestUtils.mOnAnswerCallbackCalled)
     }
 
     @Suppress("deprecation")
     private fun verifyRejectHoldCall() {
         assertFalse(TestUtils.mOnSetInactiveCallbackCalled)
-        runBlocking {
-            mCallsManager.addCall(
-                TestUtils.INCOMING_CALL_ATTRIBUTES,
-                TestUtils.mOnAnswerLambda,
-                TestUtils.mOnDisconnectLambda,
-                TestUtils.mOnSetActiveLambda,
-                TestUtils.mOnSetInActiveLambda) {
-                TestUtils.mCompleteOnSetInactive = false
-                launch {
-                    val call = TestUtils.waitOnInCallServiceToReachXCalls(1)
-                    assertNotNull("The returned Call object is <NULL>", call)
-                    answer(CallAttributes.AUDIO_CALL)
-                    TestUtils.waitOnCallState(call!!, Call.STATE_ACTIVE)
-                    call.hold()
-                    delay(TestUtils.WAIT_ON_CALL_STATE_TIMEOUT)
-                    // Request to hold call should be disregarded
-                    assertTrue(call.state == Call.STATE_ACTIVE)
-                    // always send the disconnect signal if possible
-                    assertTrue(disconnect(DisconnectCause(DisconnectCause.LOCAL)))
+        var catchBlockWasEntered = false
+        try {
+            runBlocking {
+                mCallsManager.addCall(
+                    TestUtils.INCOMING_CALL_ATTRIBUTES,
+                    TestUtils.mOnAnswerLambda,
+                    TestUtils.mOnDisconnectLambda,
+                    TestUtils.mOnSetActiveLambda,
+                    TestUtils.mOnSetInActiveLambda
+                ) {
+                    TestUtils.mCompleteOnSetInactive = false
+                    launch {
+                        val call = TestUtils.waitOnInCallServiceToReachXCalls(1)
+                        assertNotNull("The returned Call object is <NULL>", call)
+                        answer(CallAttributes.AUDIO_CALL)
+                        TestUtils.waitOnCallState(call!!, Call.STATE_ACTIVE)
+                        call.hold()
+                    }
                 }
             }
+        } catch (e: Exception) {
+            catchBlockWasEntered = true
         }
+        assertTrue(catchBlockWasEntered)
         // Assert that the correct callback was invoked
         assertTrue(TestUtils.mOnSetInactiveCallbackCalled)
     }
@@ -477,31 +504,41 @@
     @Suppress("deprecation")
     private fun verifyRejectUnholdCall() {
         assertFalse(TestUtils.mOnSetActiveCallbackCalled)
-        runBlocking {
-            mCallsManager.addCall(
-                TestUtils.INCOMING_CALL_ATTRIBUTES,
-                TestUtils.mOnAnswerLambda,
-                TestUtils.mOnDisconnectLambda,
-                TestUtils.mOnSetActiveLambda,
-                TestUtils.mOnSetInActiveLambda) {
-                launch {
-                    val call = TestUtils.waitOnInCallServiceToReachXCalls(1)
-                    assertNotNull("The returned Call object is <NULL>", call)
-                    answer(CallAttributes.AUDIO_CALL) // API under test
-                    TestUtils.waitOnCallState(call!!, Call.STATE_ACTIVE)
-                    // Fail #onSetActive after call has successfully moved to the active state
-                    TestUtils.mCompleteOnSetActive = false
-                    setInactive()
-                    TestUtils.waitOnCallState(call, Call.STATE_HOLDING)
-                    call.unhold()
-                    delay(TestUtils.WAIT_ON_CALL_STATE_TIMEOUT)
-                    // Request to unhold call should be disregarded
-                    assertTrue(call.state == Call.STATE_HOLDING)
-                    // always send the disconnect signal if possible
-                    assertTrue(disconnect(DisconnectCause(DisconnectCause.LOCAL)))
+        var catchBlockWasEntered = false
+        try {
+            runBlocking {
+                mCallsManager.addCall(
+                    TestUtils.INCOMING_CALL_ATTRIBUTES,
+                    TestUtils.mOnAnswerLambda,
+                    TestUtils.mOnDisconnectLambda,
+                    TestUtils.mOnSetActiveLambda,
+                    TestUtils.mOnSetInActiveLambda
+                ) {
+                    launch {
+                        val call = TestUtils.waitOnInCallServiceToReachXCalls(1)
+                        assertNotNull("The returned Call object is <NULL>", call)
+                        answer(CallAttributes.AUDIO_CALL) // API under test
+                        TestUtils.waitOnCallState(call!!, Call.STATE_ACTIVE)
+                        // Fail #onSetActive after call has successfully moved to the active state
+                        TestUtils.mCompleteOnSetActive = false
+                        setInactive()
+                        TestUtils.waitOnCallState(call, Call.STATE_HOLDING)
+                        call.unhold()
+                        delay(TestUtils.WAIT_ON_CALL_STATE_TIMEOUT)
+                        // Request to unhold call should be disregarded
+                        assertTrue(call.state == Call.STATE_HOLDING)
+                        // always send the disconnect signal if possible
+                        Assert.assertEquals(
+                            CallControlResult.Success(),
+                            disconnect(DisconnectCause(DisconnectCause.LOCAL))
+                        )
+                    }
                 }
             }
+        } catch (e: Exception) {
+            catchBlockWasEntered = true
         }
+        assertTrue(catchBlockWasEntered)
         // Assert that the correct callback was invoked
         assertTrue(TestUtils.mOnSetActiveCallbackCalled)
     }
@@ -509,32 +546,39 @@
     @Suppress("deprecation")
     private fun verifyRejectDisconnectCall(invokeDisconnect: Boolean) {
         assertFalse(TestUtils.mOnDisconnectCallbackCalled)
-        runBlocking {
-            mCallsManager.addCall(
-                TestUtils.INCOMING_CALL_ATTRIBUTES,
-                TestUtils.mOnAnswerLambda,
-                TestUtils.mOnDisconnectLambda,
-                TestUtils.mOnSetActiveLambda,
-                TestUtils.mOnSetInActiveLambda) {
-                TestUtils.mCompleteOnDisconnect = false
-                launch {
-                    val call = TestUtils.waitOnInCallServiceToReachXCalls(1)
-                    assertNotNull("The returned Call object is <NULL>", call)
-                    if (invokeDisconnect) {
-                        answer(CallAttributes.AUDIO_CALL) // API under test
-                        TestUtils.waitOnCallState(call!!, Call.STATE_ACTIVE)
-                        call.disconnect()
-                    } else {
-                        call!!.reject(true, "REJECT_REASON_DECLINED")
+        var catchBlockWasEntered = false
+        try {
+            runBlocking {
+                mCallsManager.addCall(
+                    TestUtils.INCOMING_CALL_ATTRIBUTES,
+                    TestUtils.mOnAnswerLambda,
+                    TestUtils.mOnDisconnectLambda,
+                    TestUtils.mOnSetActiveLambda,
+                    TestUtils.mOnSetInActiveLambda
+                ) {
+                    TestUtils.mCompleteOnDisconnect = false
+                    launch {
+                        val call = TestUtils.waitOnInCallServiceToReachXCalls(1)
+                        assertNotNull("The returned Call object is <NULL>", call)
+                        if (invokeDisconnect) {
+                            answer(CallAttributes.AUDIO_CALL) // API under test
+                            TestUtils.waitOnCallState(call!!, Call.STATE_ACTIVE)
+                            call.disconnect()
+                        } else {
+                            call!!.reject(true, "REJECT_REASON_DECLINED")
+                        }
+                        delay(TestUtils.WAIT_ON_CALL_STATE_TIMEOUT)
+                        // Rejecting the onDisconnect callback should still result in a disconnect.
+                        TestUtils.waitOnCallState(call, Call.STATE_DISCONNECTED)
+                        // always send the disconnect signal if possible
+                        disconnect(DisconnectCause(DisconnectCause.LOCAL))
                     }
-                    delay(TestUtils.WAIT_ON_CALL_STATE_TIMEOUT)
-                    // Rejecting the onDisconnect callback should still result in a disconnect.
-                    TestUtils.waitOnCallState(call, Call.STATE_DISCONNECTED)
-                    // always send the disconnect signal if possible
-                    disconnect(DisconnectCause(DisconnectCause.LOCAL))
                 }
             }
+        } catch (e: Exception) {
+            catchBlockWasEntered = true
         }
+        assertTrue(catchBlockWasEntered)
         // Assert that the correct callback was invoked
         assertTrue(TestUtils.mOnDisconnectCallbackCalled)
     }
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/BasicCallControlsTest.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/BasicCallControlsTest.kt
index 45daaeb..ad7b4a5 100644
--- a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/BasicCallControlsTest.kt
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/BasicCallControlsTest.kt
@@ -21,6 +21,7 @@
 import android.telecom.DisconnectCause
 import androidx.annotation.RequiresApi
 import androidx.core.telecom.CallAttributesCompat
+import androidx.core.telecom.CallControlResult
 import androidx.core.telecom.CallControlScope
 import androidx.core.telecom.CallEndpointCompat
 import androidx.core.telecom.internal.utils.Utils
@@ -36,7 +37,9 @@
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
 import org.junit.After
+import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotEquals
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertTrue
 import org.junit.Before
@@ -293,12 +296,14 @@
                     val call = TestUtils.waitOnInCallServiceToReachXCalls(1)
                     assertNotNull("The returned Call object is <NULL>", call)
                     if (callAttributesCompat.isOutgoingCall()) {
-                        assertTrue(setActive())
+                        assertEquals(CallControlResult.Success(), setActive())
                     } else {
-                        assertTrue(answer(CallAttributesCompat.CALL_TYPE_AUDIO_CALL))
+                        assertEquals(CallControlResult.Success(),
+                            answer(CallAttributesCompat.CALL_TYPE_AUDIO_CALL))
                     }
                     TestUtils.waitOnCallState(call!!, Call.STATE_ACTIVE)
-                    assertTrue(disconnect(DisconnectCause(DisconnectCause.LOCAL)))
+                    assertEquals(CallControlResult.Success(),
+                        disconnect(DisconnectCause(DisconnectCause.LOCAL)))
                 }
             }
         }
@@ -312,12 +317,15 @@
                     val call = TestUtils.waitOnInCallServiceToReachXCalls(1)
                     assertNotNull("The returned Call object is <NULL>", call)
                     repeat(NUM_OF_TIMES_TO_TOGGLE) {
-                        assertTrue(setActive())
+                        assertEquals(CallControlResult.Success(), setActive())
                         TestUtils.waitOnCallState(call!!, Call.STATE_ACTIVE)
-                        assertTrue(setInactive())
+                        assertEquals(CallControlResult.Success(), setInactive())
                         TestUtils.waitOnCallState(call, Call.STATE_HOLDING)
                     }
-                    assertTrue(disconnect(DisconnectCause(DisconnectCause.LOCAL)))
+                    assertEquals(
+                        CallControlResult.Success(),
+                        disconnect(DisconnectCause(DisconnectCause.LOCAL))
+                    )
                 }
             }
         }
@@ -329,10 +337,13 @@
                 launch {
                     val call = TestUtils.waitOnInCallServiceToReachXCalls(1)
                     assertNotNull("The returned Call object is <NULL>", call)
-                    assertTrue(setActive())
+                    assertEquals(CallControlResult.Success(), setActive())
                     TestUtils.waitOnCallState(call!!, Call.STATE_ACTIVE)
-                    assertFalse(setInactive()) // API under test / expect failure
-                    assertTrue(disconnect(DisconnectCause(DisconnectCause.LOCAL)))
+                    assertNotEquals(CallControlResult.Success(), setInactive())
+                    assertEquals(
+                        CallControlResult.Success(),
+                        disconnect(DisconnectCause(DisconnectCause.LOCAL))
+                    )
                 }
             }
         }
@@ -357,11 +368,17 @@
                             getAnotherEndpoint(currentEndpoint, availableEndpointsList)
                         assertNotNull(anotherEndpoint)
                         // set the call active
-                        assertTrue(setActive())
+                        assertEquals(CallControlResult.Success(), setActive())
                         // request an endpoint switch
-                        assertTrue(requestEndpointChange(anotherEndpoint!!))
+                        assertEquals(
+                            CallControlResult.Success(),
+                            requestEndpointChange(anotherEndpoint!!)
+                        )
                     }
-                    assertTrue(disconnect(DisconnectCause(DisconnectCause.LOCAL)))
+                    assertEquals(
+                        CallControlResult.Success(),
+                        disconnect(DisconnectCause(DisconnectCause.LOCAL))
+                    )
                 }
             }
         }
@@ -382,7 +399,7 @@
                 launch {
                     val call = TestUtils.waitOnInCallServiceToReachXCalls(1)
                     assertNotNull("The returned Call object is <NULL>", call)
-                    assertTrue(setActive())
+                    assertEquals(CallControlResult.Success(), setActive())
                     TestUtils.waitOnCallState(call!!, Call.STATE_ACTIVE)
                     // Grab initial mute state
                     val initialMuteState = isMuted.first()
@@ -405,7 +422,8 @@
                     }
                     // Ensure that the updated mute state was collected
                     assertTrue(muteStateChanged)
-                    assertTrue(disconnect(DisconnectCause(DisconnectCause.LOCAL)))
+                    assertEquals(CallControlResult.Success(),
+                        disconnect(DisconnectCause(DisconnectCause.LOCAL)))
                 }
             }
         }
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
index 458a920..1488565 100644
--- 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
@@ -22,6 +22,7 @@
 import android.telecom.DisconnectCause
 import androidx.annotation.RequiresApi
 import androidx.core.telecom.CallAttributesCompat
+import androidx.core.telecom.CallControlResult
 import androidx.core.telecom.CallsManager
 import androidx.core.telecom.internal.InCallServiceCompat
 import androidx.core.telecom.internal.utils.Utils
@@ -159,18 +160,24 @@
         runBlocking {
             assertWithinTimeout_addCall(callAttributesCompat) {
                 launch {
-                    val call = TestUtils.waitOnInCallServiceToReachXCalls(1)
-                    Assert.assertNotNull("The returned Call object is <NULL>", call!!)
+                    try {
+                        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)
+                        // 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)
+                    } finally {
+                        // Always send disconnect signal if possible.
+                        assertEquals(
+                            CallControlResult.Success(),
+                            disconnect(DisconnectCause(DisconnectCause.LOCAL))
+                        )
                     }
-
-                    // Assert the call extra or call property from the details
-                    assertCallExtraOrProperty(call)
-                    // Always send disconnect signal if possible.
-                    assertTrue(disconnect(DisconnectCause(DisconnectCause.LOCAL)))
                 }
             }
         }
diff --git a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/InCallAudioTest.kt b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/InCallAudioTest.kt
index e8ac34e..5962666 100644
--- a/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/InCallAudioTest.kt
+++ b/core/core-telecom/src/androidTest/java/androidx/core/telecom/test/InCallAudioTest.kt
@@ -21,6 +21,7 @@
 import android.telecom.DisconnectCause
 import android.util.Log
 import androidx.annotation.RequiresApi
+import androidx.core.telecom.CallControlResult
 import androidx.core.telecom.internal.utils.Utils
 import androidx.core.telecom.test.utils.BaseTelecomTest
 import androidx.core.telecom.test.utils.TestUtils
@@ -130,7 +131,8 @@
                         yield() // mechanism to stop the while loop if the coroutine is dead
                         delay(1) // sleep x millisecond(s) instead of spamming check
                     }
-                    Assert.assertTrue(disconnect(DisconnectCause(DisconnectCause.LOCAL)))
+                    Assert.assertEquals(CallControlResult.Success(),
+                        disconnect(DisconnectCause(DisconnectCause.LOCAL)))
                 }
             }
         }
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
index 94f3a2e..30fda2f 100644
--- 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
@@ -23,6 +23,7 @@
 import android.util.Log
 import androidx.annotation.RequiresApi
 import androidx.core.telecom.CallAttributesCompat
+import androidx.core.telecom.CallControlResult
 import androidx.core.telecom.CallsManager
 import androidx.core.telecom.internal.InCallServiceCompat
 import androidx.core.telecom.internal.utils.Utils
@@ -216,7 +217,9 @@
                     // Assert call extension type.
                     assertEquals(expectedType, inCallServiceCompat.resolveCallExtensionsType(call))
                     // Always send disconnect signal if possible.
-                    Assert.assertTrue(disconnect(DisconnectCause(DisconnectCause.LOCAL)))
+                    assertEquals(
+                        CallControlResult.Success(),
+                        disconnect(DisconnectCause(DisconnectCause.LOCAL)))
                 }
             }
         }
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 c44414ca..dc1455a 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
@@ -61,7 +61,7 @@
     val VERIFICATION_TIMEOUT_MSG =
         "Timed out before asserting all values. This most likely means the platform failed to" +
             " add the call or hung on a CallControl operation."
-
+    val CALLBACK_FAILED_EXCEPTION_MSG = "callback failed to be completed in the lambda function"
     // non-primitive constants
     val TEST_PHONE_NUMBER_9001 = Uri.parse("tel:6506959001")
     val TEST_PHONE_NUMBER_8985 = Uri.parse("tel:6506958985")
@@ -133,28 +133,37 @@
         }
     }
 
-    val mOnSetActiveLambda: suspend () -> Boolean = {
+    val mOnSetActiveLambda: suspend () -> Unit = {
         Log.i(LOG_TAG, "onSetActive: completing")
         mOnSetActiveCallbackCalled = true
-        mCompleteOnSetActive
+        if (!mCompleteOnSetActive) {
+            throw Exception(CALLBACK_FAILED_EXCEPTION_MSG)
+        }
     }
 
-    val mOnSetInActiveLambda: suspend () -> Boolean = {
+    val mOnSetInActiveLambda: suspend () -> Unit = {
         Log.i(LOG_TAG, "onSetInactive: completing")
         mOnSetInactiveCallbackCalled = true
-        mCompleteOnSetInactive
+        if (!mCompleteOnSetInactive) {
+            throw Exception(CALLBACK_FAILED_EXCEPTION_MSG)
+        }
     }
 
-    val mOnAnswerLambda: suspend (type: Int) -> Boolean = {
+    val mOnAnswerLambda: suspend (type: Int) -> Unit = {
         Log.i(LOG_TAG, "onAnswer: callType=[$it]")
         mOnAnswerCallbackCalled = true
-        mCompleteOnAnswer
+        if (!mCompleteOnAnswer) {
+            throw Exception(CALLBACK_FAILED_EXCEPTION_MSG)
+        }
     }
 
-    val mOnDisconnectLambda: suspend (cause: DisconnectCause) -> Boolean = {
+    val mOnDisconnectLambda: suspend (cause: DisconnectCause) -> Unit = {
         Log.i(LOG_TAG, "onDisconnect: disconnectCause=[$it]")
         mOnDisconnectCallbackCalled = true
         mCompleteOnDisconnect
+        if (!mCompleteOnDisconnect) {
+            throw Exception(CALLBACK_FAILED_EXCEPTION_MSG)
+        }
     }
 
     // Flags for determining whether the given callback was invoked or not
@@ -318,7 +327,8 @@
     suspend fun waitOnCallExtras(call: Call) {
         try {
             withTimeout(TestUtils.WAIT_ON_CALL_STATE_TIMEOUT) {
-                while (isActive /* aka  within timeout window */ && call.details?.extras == null) {
+                while (isActive /* aka  within timeout window */ && (
+                        call.details?.extras == null || call.details.extras.isEmpty)) {
                     yield() // another mechanism to stop the while loop if the coroutine is dead
                     delay(1) // sleep x millisecond(s) instead of spamming check
                 }
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/CallControlResult.kt b/core/core-telecom/src/main/java/androidx/core/telecom/CallControlResult.kt
new file mode 100644
index 0000000..4dfb2e3
--- /dev/null
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/CallControlResult.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.telecom
+
+import java.util.Objects
+
+/**
+ * CallControlResult is a return value that represents the result of one of the following
+ * [CallControlScope] methods:
+ * - [CallControlScope.setActive]
+ * - [CallControlScope.answer]
+ * - [CallControlScope.disconnect]
+ * - [CallControlScope.setInactive]
+ * - [CallControlScope.requestEndpointChange]
+ *
+ * Each of the above listed methods has the ability to fail and if it does, this will be represented
+ * by a [CallControlResult.Error] (e.g. Telecom was not able to change the call route via
+ * requestEndpointChange). Otherwise, [CallControlResult.Success] is returned to represent the
+ * operation succeeded (e.g Telecom was able to set the call active).
+ *
+ * Example usage:
+ * ```
+ * launch {
+ *     when(val result = setActive()) {
+ *       is CallControlResult.Success -> {
+ *           Log.d(TAG, "onSetActive - ${result}")
+ *           // move call to active state locally
+ *       }
+ *       is CallControlResult.Failure -> {
+ *           Log.w(TAG, "onSetActive - ${result}")
+ *          // surface error to user if required
+ *       }
+ * }
+ * ````
+ **/
+sealed class CallControlResult {
+    /**
+     * The associated [CallControlScope] method was successful. For example, if
+     * [CallControlScope.setActive] was requested, Telecom was able to change the call state.
+     */
+    class Success : CallControlResult() {
+        override fun toString(): String {
+            return "CallControlResult(Success)"
+        }
+
+        override fun equals(other: Any?): Boolean {
+            return other is Success
+        }
+
+        override fun hashCode(): Int {
+            return Objects.hash()
+        }
+    }
+
+    /**
+     * The associated [CallControlScope] method failed. For example, if
+     * [CallControlScope.setActive] was requested, Telecom failed to transition the call to active.
+     * There are numerous reasons why the operation failed; please see the [errorCode] for details.
+     */
+    class Error(@CallException.Companion.CallErrorCode val errorCode: Int) : CallControlResult() {
+        override fun toString(): String {
+            return "CallControlResult(Error[errorCode=($errorCode)])"
+        }
+
+        override fun equals(other: Any?): Boolean {
+            return other is Error && errorCode == other.errorCode
+        }
+
+        override fun hashCode(): Int {
+            return Objects.hash(errorCode)
+        }
+    }
+}
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/CallControlScope.kt b/core/core-telecom/src/main/java/androidx/core/telecom/CallControlScope.kt
index c90b9b4..ebfd735 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/CallControlScope.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/CallControlScope.kt
@@ -25,23 +25,23 @@
  * used to provide updates to the call state and receive updates about a call state.  Example usage:
  *
  *    // initiate a call and control via the CallControlScope
- *    mCallsManager.addCall(callAttributes) { // This block represents the CallControlScope
+ *    mCallsManager.addCall(
+ *        callAttributes,
+ *        onAnswerLambda,
+ *        onDisconnectLambda,
+ *        onSetActiveLambda,
+ *        onSetInActiveLambda
+ *        ) { // This block represents the CallControlScope
  *
- *          // set your implementation of [CallControlCallback]
- *         setCallback(myCallControlCallbackImplementation)
- *
- *         // UI flow sends an update to a call state, relay the update to Telecom
- *         disconnectCallButton.setOnClickListener {
+ *           // UI flow sends an update to a call state, relay the update to Telecom
+ *           disconnectCallButton.setOnClickListener {
  *             val wasSuccessful = disconnect(reason) // waits for telecom async. response
  *             // update UI
- *         }
+ *           }
  *
- *         // Collect updates
- *         launch {
- *             currentCallEndpoint.collect { // access the new [CallEndpoint] here }
- *         }
+ *           // Collect updates
+ *           currentCallEndpoint.collect { // access the new [CallEndpoint] here }
  *     }
- *
  */
 interface CallControlScope : CoroutineScope {
     /**
@@ -55,20 +55,22 @@
      * when either an outgoing call is ready to go active or a held call is ready to go active
      * again. For incoming calls that are ready to be answered, use [answer].
      *
-     * Telecom will return true if your app is able to set the call active.  Otherwise false will
-     * be returned (ex. another call is active and telecom cannot set this call active until the
-     * other call is held or disconnected)
+     * @return Telecom will return [CallControlResult.Success] if your app is able to set the call
+     * active. Otherwise [CallControlResult.Error] will be returned (ex. another call is active and
+     * telecom cannot set this call active until the other call is held or disconnected) with an
+     * error code indicating why setActive failed.
      */
-    suspend fun setActive(): Boolean
+    suspend fun setActive(): CallControlResult
 
     /**
      * Inform Telecom that your app wants to make this call inactive. This the same as hold for two
      * call endpoints but can be extended to setting a meeting to inactive.
      *
-     * Telecom will return true if your app is able to set the call inactive. Otherwise, false will
-     * be returned.
+     * @return Telecom will return [CallControlResult.Success] if your app is able to set the call
+     * inactive. Otherwise,  [CallControlResult.Error] will be returned with an error code
+     * indicating why setInActive failed.
      */
-    suspend fun setInactive(): Boolean
+    suspend fun setInactive(): CallControlResult
 
     /**
      * Inform Telecom that your app wants to make this incoming call active.  For outgoing calls
@@ -76,12 +78,13 @@
      *
      * @param [callType] that call is to be answered as.
      *
-     * Telecom will return true if your app is able to answer the call.  Otherwise false will
-     * be returned (ex. another call is active and telecom cannot set this call active until the
-     * other call is held or disconnected) which means that your app cannot answer this call at
+     * @return Telecom will return [CallControlResult.Success] if your app is able to answer the
+     * call. Otherwise [CallControlResult.Error] will be returned with an  error code indicating
+     * why answer failed (ex. another call is active and telecom cannot set this call active until
+     * the other call is held or disconnected). This means that your app cannot answer this call at
      * this time.
      */
-    suspend fun answer(@CallAttributesCompat.Companion.CallType callType: Int): Boolean
+    suspend fun answer(@CallAttributesCompat.Companion.CallType callType: Int): CallControlResult
 
     /**
      * Inform Telecom that your app wishes to disconnect the call and remove the call from telecom
@@ -96,13 +99,11 @@
      *                        <li>[DisconnectCause#MISSED]</li>
      *                        </ul>
      *
-     * Telecom will always return true unless the call has already been disconnected.
-     *
-     * <p>
-     * Note: After the call has been successfully disconnected, calling any [CallControlScope] will
-     * result in a false to be returned.
+     * @return [CallControlResult.Success] will be returned if Telecom is able to disconnect
+     * the call successfully. Otherwise [CallControlResult.Error] will be returned with an error
+     * code indicating why disconnect failed.
      */
-    suspend fun disconnect(disconnectCause: android.telecom.DisconnectCause): Boolean
+    suspend fun disconnect(disconnectCause: android.telecom.DisconnectCause): CallControlResult
 
     /**
      * Request a [CallEndpointCompat] change. Clients should not define their own [CallEndpointCompat] when
@@ -111,10 +112,11 @@
      *
      * @param endpoint The [CallEndpointCompat] to change to.
      *
-     * @return true if Telecom is able to switch to the requested endpoint successfully.  Otherwise,
-     * false will be returned to represent a failure.
+     * @return [CallControlResult.Success] will be returned if Telecom is able to switch to the
+     * requested endpoint successfully.  Otherwise, [CallControlResult.Error] will be returned with
+     * an error code indicating why disconnect failed.
      */
-    suspend fun requestEndpointChange(endpoint: CallEndpointCompat): Boolean
+    suspend fun requestEndpointChange(endpoint: CallEndpointCompat): CallControlResult
 
     /**
      * Collect the new [CallEndpointCompat] through which call media flows (i.e. speaker,
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/CallException.kt b/core/core-telecom/src/main/java/androidx/core/telecom/CallException.kt
index 75506f2..3677333 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/CallException.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/CallException.kt
@@ -22,40 +22,91 @@
 /**
  * This class defines exceptions that can be thrown when using [androidx.core.telecom] APIs.
  */
-class CallException(
-    @CallErrorCode val code: Int = ERROR_UNKNOWN_CODE,
-    message: String? = codeToMessage(code)
-) : RuntimeException(message) {
+class CallException(@CallErrorCode val code: Int = ERROR_UNKNOWN) : RuntimeException() {
 
     override fun toString(): String {
-        return "CallException( code=[$code], message=[$message])"
+        return "CallException(code=[$code])"
+    }
+
+    override fun equals(other: Any?): Boolean {
+        return other is CallException && code == other.code
+    }
+
+    override fun hashCode(): Int {
+        return code.hashCode()
     }
 
     companion object {
         @RestrictTo(RestrictTo.Scope.LIBRARY)
         @Retention(AnnotationRetention.SOURCE)
-        @IntDef(ERROR_BUILD_VERSION_CODE, ERROR_UNKNOWN_CODE)
+        @IntDef(ERROR_UNKNOWN,
+            ERROR_CANNOT_HOLD_CURRENT_ACTIVE_CALL, ERROR_CALL_IS_NOT_BEING_TRACKED,
+            ERROR_CALL_CANNOT_BE_SET_TO_ACTIVE, ERROR_CALL_NOT_PERMITTED_AT_PRESENT_TIME,
+            ERROR_OPERATION_TIMED_OUT, ERROR_CALL_DOES_NOT_SUPPORT_HOLD,
+            ERROR_BLUETOOTH_DEVICE_IS_NULL)
         annotation class CallErrorCode
 
         /**
          * The operation has failed due to an unknown or unspecified error.
          */
-        const val ERROR_BUILD_VERSION_CODE = 0
-
-        internal const val ERROR_BUILD_VERSION_MSG: String = "Core-Telecom only supports builds" +
-            " from Oreo (Android 8) and above.  In order to utilize Core-Telecom, your device" +
-            " must be updated."
+        const val ERROR_UNKNOWN = 1
 
         /**
-         * The operation has failed due to an unknown or unspecified error.
+         * The operation has failed due to Telecom failing to hold the current active call for the
+         * call attempting to become the new active call.  The client should end the current active
+         * call and re-try the failed operation.
          */
-        const val ERROR_UNKNOWN_CODE = 1
+        const val ERROR_CANNOT_HOLD_CURRENT_ACTIVE_CALL = 2
 
-        internal fun codeToMessage(@CallErrorCode code: Int): String {
+        /**
+         * The operation has failed because Telecom has already removed the call from the server
+         * side and destroyed all the objects associated with it.  The client should re-add the
+         * call.
+         */
+        const val ERROR_CALL_IS_NOT_BEING_TRACKED = 3
+
+        /**
+         * The operation has failed because Telecom cannot set the requested call as the current
+         * active call.  The client should end the current active call and re-try the operation.
+         */
+        const val ERROR_CALL_CANNOT_BE_SET_TO_ACTIVE = 4
+
+        /**
+         * The operation has failed because there is either no PhoneAccount registered with Telecom
+         * for the given operation, or the limit of calls has been reached. The client should end
+         * the current active call and re-try the failed operation.
+         */
+        const val ERROR_CALL_NOT_PERMITTED_AT_PRESENT_TIME = 5
+
+        /**
+         * The operation has failed because the operation failed to complete before the timeout
+         */
+        const val ERROR_OPERATION_TIMED_OUT = 6
+
+        /**
+         * The [CallControlScope.setInactive] or [CallsManager.addCall#onSetInactive] failed
+         * because the [CallAttributesCompat.SUPPORTS_SET_INACTIVE] was not set.  Please re-add
+         * the call with the [CallAttributesCompat.SUPPORTS_SET_INACTIVE] if the call should
+         * be able to hold.
+         */
+        const val ERROR_CALL_DOES_NOT_SUPPORT_HOLD = 7
+
+        /**
+         * Telecom was not able to switch the audio route to Bluetooth because the Bluetooth device
+         * is null. The user should reconnect the Bluetooth device and retry the audio route switch.
+         */
+        const val ERROR_BLUETOOTH_DEVICE_IS_NULL = 8
+
+        internal fun fromTelecomCode(code: Int): Int {
             when (code) {
-                ERROR_BUILD_VERSION_CODE -> return ERROR_BUILD_VERSION_MSG
+                1 -> return ERROR_UNKNOWN
+                2 -> return ERROR_CANNOT_HOLD_CURRENT_ACTIVE_CALL
+                3 -> return ERROR_CALL_IS_NOT_BEING_TRACKED
+                4 -> return ERROR_CALL_CANNOT_BE_SET_TO_ACTIVE
+                5 -> return ERROR_CALL_NOT_PERMITTED_AT_PRESENT_TIME
+                6 -> return ERROR_OPERATION_TIMED_OUT
             }
-            return "An Unknown Error has occurred while using the Core-Telecom APIs"
+            return ERROR_UNKNOWN
         }
     }
 }
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 27df20c..716bebc 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
@@ -195,72 +195,58 @@
      * it is placed the background. Foreground execution priority is removed from your app when all
      * of your app's calls terminate or your app no longer posts a valid notification.
      *
-     * Note: For outgoing calls, your application should either immediately post a
-     * [android.app.Notification.CallStyle] notification or delay adding the call via this
-     * addCall method until the remote side is ready.
+     * - Other things that should be noted:
+     *     - For outgoing calls, your application should either immediately post a
+     *       [android.app.Notification.CallStyle] notification or delay adding the call via this
+     *       addCall method until the remote side is ready.
+     *     - Each lambda function (onAnswer, onDisconnect, onSetActive, onSetInactive) has a
+     *       timeout of 5000 milliseconds. Failing to complete the suspend fun before the timeout
+     *       will result in a failed transaction.
+     *     - Telecom assumes each callback (onAnswer, onDisconnect, onSetActive, onSetInactive)
+     *       is handled successfully on the client side. If the callback cannot be completed,
+     *       an Exception should be thrown. Telecom will rethrow the Exception and tear down
+     *       the call session.
+     *     - Each lambda function (onAnswer, onDisconnect, onSetActive, onSetInactive) has a
+     *       timeout of 5000 milliseconds. Failing to complete the suspend fun before the
+     *       timeout will result in a failed transaction.
      *
      * @param callAttributes     attributes of the new call (incoming or outgoing, address, etc. )
-     * @param block              DSL interface block that will run when the call is ready
-     * @param onAnswer           Telecom is informing your VoIP application to answer an incoming
-     *                           call and  set it to active. Telecom is requesting this on behalf
-     *                           of an system service (e.g. Automotive service) or a device (e.g.
-     *                           Wearable).
      *
-     *                           @param callType that call is requesting to be answered as.
+     * @param onAnswer           where callType is the audio/video state the call should be
+     *                           answered as.  Telecom is informing your VoIP application to answer
+     *                           an incoming call and  set it to active. Telecom is requesting this
+     *                           on behalf of an system service (e.g. Automotive service) or a
+     *                           device (e.g. Wearable).
      *
-     *                           @return true to indicate your VoIP application can answer the
-     *                           call with the given [CallAttributesCompat.Companion.CallType].
-     *                           Otherwise, return false to indicate your application is unable to
-     *                           process the request and telecom will cancel the external request.
+     * @param onDisconnect       where disconnectCause represents the cause for disconnecting the
+     *                           call. Telecom is informing your VoIP application to disconnect the
+     *                           incoming call. Telecom is requesting this on behalf of an system
+     *                           service (e.g. Automotive service) or a device (e.g. Wearable).
      *
-     * @param onDisconnect       Telecom is informing your VoIP application to disconnect the
-     *                           incoming  call and set it to active. Telecom is requesting this on
-     *                           behalf of an system service (e.g. Automotive service) or a device
-     *                           (e.g. Wearable).
-     *
-     *                           @param disconnectCause represents the cause for disconnecting the
-     *                           call.
-     *
-     *                           @return true when your VoIP application has disconnected the call.
-     *                           Otherwise, return false to indicate your application is unable to
-     *                           process the request. However, telecom will still
      * @param onSetActive        Telecom is informing your VoIP application to set the call active.
      *                           Telecom is requesting this on behalf of an system service (e.g.
      *                           Automotive service) or a device (e.g. Wearable).
      *
-     *                           @return true to indicate your VoIP application can set the call
-     *                           (that corresponds to this lambda function) to active.
-     *                           Otherwise, return false to indicate your application is unable to
-     *                           process the request and telecom will cancel the external request.
-     *
      * @param onSetInactive      Telecom is informing your VoIP application to set the call
      *                           inactive. This is the same as holding a call for two endpoints but
      *                           can be extended to setting a meeting inactive. Telecom is
      *                           requesting this on behalf of an system service (e.g. Automotive
      *                           service) or a device (e.g.Wearable). Note: Your app must stop
      *                           using the microphone and playing incoming media when returning.
+     * @param block              DSL interface block that will run when the call is ready
      *
-     *                           @return true to indicate your VoIP application can set the call
-     *                           (that corresponds to this lambda function) to inactive.
-     *                           Otherwise, return false to indicate your application is unable to
-     *                           process the request and telecom will cancel the external request.
-     *
-     * Note: Each lambda function (onAnswer, onDisconnect, onSetActive, onSetInactive) has a
-     * timeout of 5000 milliseconds. Failing to complete the suspend fun before the timeout will
-     * result in a failed transaction.
-     *
-     * @Throws UnsupportedOperationException if the device is on an invalid build
-     * @Throws CancellationException if the call failed to be added within 5000 milliseconds
+     * @throws UnsupportedOperationException if the device is on an invalid build
+     * @throws CancellationException if the call failed to be added within 5000 milliseconds
      */
     @RequiresPermission(value = "android.permission.MANAGE_OWN_CALLS")
     @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
     @Suppress("ClassVerificationFailure")
     suspend fun addCall(
         callAttributes: CallAttributesCompat,
-        onAnswer: suspend (callType: @CallAttributesCompat.Companion.CallType Int) -> Boolean,
-        onDisconnect: suspend (disconnectCause: android.telecom.DisconnectCause) -> Boolean,
-        onSetActive: suspend () -> Boolean,
-        onSetInactive: suspend () -> Boolean,
+        onAnswer: suspend (callType: @CallAttributesCompat.Companion.CallType Int) -> Unit,
+        onDisconnect: suspend (disconnectCause: android.telecom.DisconnectCause) -> Unit,
+        onSetActive: suspend () -> Unit,
+        onSetInactive: suspend () -> Unit,
         block: CallControlScope.() -> Unit
     ) {
         // This API is not supported for device running anything below Android O (26)
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSession.kt b/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSession.kt
index 52901d2..00e5817 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSession.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSession.kt
@@ -22,6 +22,7 @@
 import android.telecom.CallException
 import android.telecom.DisconnectCause
 import androidx.annotation.RequiresApi
+import androidx.core.telecom.CallControlResult
 import androidx.core.telecom.CallControlScope
 import androidx.core.telecom.CallEndpointCompat
 import androidx.core.telecom.internal.utils.EndpointUtils
@@ -38,10 +39,10 @@
 @Suppress("ClassVerificationFailure")
 internal class CallSession(
     coroutineContext: CoroutineContext,
-    val onAnswerCallback: suspend (callType: Int) -> Boolean,
-    val onDisconnectCallback: suspend (disconnectCause: DisconnectCause) -> Boolean,
-    val onSetActiveCallback: suspend () -> Boolean,
-    val onSetInactiveCallback: suspend () -> Boolean,
+    val onAnswerCallback: suspend (callType: Int) -> Unit,
+    val onDisconnectCallback: suspend (disconnectCause: DisconnectCause) -> Unit,
+    val onSetActiveCallback: suspend () -> Unit,
+    val onSetInactiveCallback: suspend () -> Unit,
     private val blockingSessionExecution: CompletableDeferred<Unit>
 ) {
     private val mCoroutineContext = coroutineContext
@@ -115,16 +116,17 @@
     /**
      * Custom OutcomeReceiver that handles the Platform responses to a CallControl API call
      */
-    inner class CallControlReceiver(deferred: CompletableDeferred<Boolean>) :
+    inner class CallControlReceiver(deferred: CompletableDeferred<CallControlResult>) :
         OutcomeReceiver<Void, CallException> {
-        private val mResultDeferred: CompletableDeferred<Boolean> = deferred
+        private val mResultDeferred: CompletableDeferred<CallControlResult> = deferred
 
         override fun onResult(r: Void?) {
-            mResultDeferred.complete(true)
+            mResultDeferred.complete(CallControlResult.Success())
         }
 
         override fun onError(error: CallException) {
-            mResultDeferred.complete(false)
+            mResultDeferred.complete(CallControlResult.Error(
+                androidx.core.telecom.CallException.fromTelecomCode(error.code)))
         }
     }
 
@@ -132,29 +134,29 @@
         return mPlatformInterface!!.callId
     }
 
-    suspend fun setActive(): Boolean {
-        val result: CompletableDeferred<Boolean> = CompletableDeferred()
+    suspend fun setActive(): CallControlResult {
+        val result: CompletableDeferred<CallControlResult> = CompletableDeferred()
         mPlatformInterface?.setActive(Runnable::run, CallControlReceiver(result))
         result.await()
         return result.getCompleted()
     }
 
-    suspend fun setInactive(): Boolean {
-        val result: CompletableDeferred<Boolean> = CompletableDeferred()
+    suspend fun setInactive(): CallControlResult {
+        val result: CompletableDeferred<CallControlResult> = CompletableDeferred()
         mPlatformInterface?.setInactive(Runnable::run, CallControlReceiver(result))
         result.await()
         return result.getCompleted()
     }
 
-    suspend fun answer(videoState: Int): Boolean {
-        val result: CompletableDeferred<Boolean> = CompletableDeferred()
+    suspend fun answer(videoState: Int): CallControlResult {
+        val result: CompletableDeferred<CallControlResult> = CompletableDeferred()
         mPlatformInterface?.answer(videoState, Runnable::run, CallControlReceiver(result))
         result.await()
         return result.getCompleted()
     }
 
-    suspend fun requestEndpointChange(endpoint: android.telecom.CallEndpoint): Boolean {
-        val result: CompletableDeferred<Boolean> = CompletableDeferred()
+    suspend fun requestEndpointChange(endpoint: android.telecom.CallEndpoint): CallControlResult {
+        val result: CompletableDeferred<CallControlResult> = CompletableDeferred()
         mPlatformInterface?.requestCallEndpointChange(
             endpoint,
             Runnable::run, CallControlReceiver(result)
@@ -163,8 +165,8 @@
         return result.getCompleted()
     }
 
-    suspend fun disconnect(disconnectCause: DisconnectCause): Boolean {
-        val result: CompletableDeferred<Boolean> = CompletableDeferred()
+    suspend fun disconnect(disconnectCause: DisconnectCause): CallControlResult {
+        val result: CompletableDeferred<CallControlResult> = CompletableDeferred()
         mPlatformInterface?.disconnect(
             disconnectCause,
             Runnable::run,
@@ -179,33 +181,57 @@
      */
     fun onSetActive(wasCompleted: Consumer<Boolean>) {
         CoroutineScope(mCoroutineContext).launch {
-            val clientResponse: Boolean = onSetActiveCallback()
-            wasCompleted.accept(clientResponse)
+            try {
+                onSetActiveCallback()
+                wasCompleted.accept(true)
+            } catch (e: Exception) {
+                handleCallbackFailure(wasCompleted, e)
+            }
         }
     }
 
     fun onSetInactive(wasCompleted: Consumer<Boolean>) {
         CoroutineScope(mCoroutineContext).launch {
-            val clientResponse: Boolean = onSetInactiveCallback()
-            wasCompleted.accept(clientResponse)
+            try {
+                onSetInactiveCallback()
+                wasCompleted.accept(true)
+            } catch (e: Exception) {
+                handleCallbackFailure(wasCompleted, e)
+            }
         }
     }
 
     fun onAnswer(videoState: Int, wasCompleted: Consumer<Boolean>) {
         CoroutineScope(mCoroutineContext).launch {
-            val clientResponse: Boolean = onAnswerCallback(videoState)
-            wasCompleted.accept(clientResponse)
+            try {
+                onAnswerCallback(videoState)
+                wasCompleted.accept(true)
+            } catch (e: Exception) {
+                handleCallbackFailure(wasCompleted, e)
+            }
         }
     }
 
     fun onDisconnect(cause: DisconnectCause, wasCompleted: Consumer<Boolean>) {
         CoroutineScope(mCoroutineContext).launch {
-            val clientResponse: Boolean = onDisconnectCallback(cause)
-            wasCompleted.accept(clientResponse)
-            blockingSessionExecution.complete(Unit)
+            try {
+                onDisconnectCallback(cause)
+                wasCompleted.accept(true)
+            } catch (e: Exception) {
+                wasCompleted.accept(false)
+                throw e
+            } finally {
+                blockingSessionExecution.complete(Unit)
+            }
         }
     }
 
+    private fun handleCallbackFailure(wasCompleted: Consumer<Boolean>, e: Exception) {
+        wasCompleted.accept(false)
+        blockingSessionExecution.complete(Unit)
+        throw e
+    }
+
     /**
      * =========================================================================================
      *  Simple implementation of [CallControlScope] with a [CallSession] as the session.
@@ -225,26 +251,26 @@
             return session.getCallId()
         }
 
-        override suspend fun setActive(): Boolean {
+        override suspend fun setActive(): CallControlResult {
             return session.setActive()
         }
 
-        override suspend fun setInactive(): Boolean {
+        override suspend fun setInactive(): CallControlResult {
             return session.setInactive()
         }
 
-        override suspend fun answer(callType: Int): Boolean {
+        override suspend fun answer(callType: Int): CallControlResult {
             return session.answer(callType)
         }
 
-        override suspend fun disconnect(disconnectCause: DisconnectCause): Boolean {
+        override suspend fun disconnect(disconnectCause: DisconnectCause): CallControlResult {
             val response = session.disconnect(disconnectCause)
             blockingSessionExecution.complete(Unit)
             return response
         }
 
         override suspend fun requestEndpointChange(endpoint: CallEndpointCompat):
-            Boolean {
+            CallControlResult {
             return session.requestEndpointChange(
                 EndpointUtils.Api34PlusImpl.toCallEndpoint(endpoint)
             )
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSessionLegacy.kt b/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSessionLegacy.kt
index 1d6f411..2142201 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSessionLegacy.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/internal/CallSessionLegacy.kt
@@ -26,8 +26,10 @@
 import android.util.Log
 import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
+import androidx.core.telecom.CallControlResult
 import androidx.core.telecom.CallControlScope
 import androidx.core.telecom.CallEndpointCompat
+import androidx.core.telecom.CallException
 import androidx.core.telecom.internal.utils.EndpointUtils
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CompletableDeferred
@@ -41,10 +43,10 @@
     private val id: ParcelUuid,
     private val callChannels: CallChannels,
     private val coroutineContext: CoroutineContext,
-    val onAnswerCallback: suspend (callType: Int) -> Boolean,
-    val onDisconnectCallback: suspend (disconnectCause: DisconnectCause) -> Boolean,
-    val onSetActiveCallback: suspend () -> Boolean,
-    val onSetInactiveCallback: suspend () -> Boolean,
+    val onAnswerCallback: suspend (callType: Int) -> Unit,
+    val onDisconnectCallback: suspend (disconnectCause: DisconnectCause) -> Unit,
+    val onSetActiveCallback: suspend () -> Unit,
+    val onSetInactiveCallback: suspend () -> Unit,
     private val blockingSessionExecution: CompletableDeferred<Unit>
 ) : android.telecom.Connection() {
     // instance vars
@@ -114,40 +116,39 @@
         return id
     }
 
-    fun answer(videoState: Int): Boolean {
+    fun answer(videoState: Int): CallControlResult {
         setVideoState(videoState)
         setActive()
-        return true
+        return CallControlResult.Success()
     }
 
-    fun setConnectionActive(): Boolean {
+    fun setConnectionActive(): CallControlResult {
         setActive()
-        return true
+        return CallControlResult.Success()
     }
 
-    fun setConnectionInactive(): Boolean {
+    fun setConnectionInactive(): CallControlResult {
         return if (this.connectionCapabilities.and(CAPABILITY_SUPPORT_HOLD)
-            == CAPABILITY_SUPPORT_HOLD
-        ) {
+            == CAPABILITY_SUPPORT_HOLD) {
             setOnHold()
-            true
+            CallControlResult.Success()
         } else {
-            false
+            CallControlResult.Error(CallException.ERROR_CALL_DOES_NOT_SUPPORT_HOLD)
         }
     }
 
-    fun setConnectionDisconnect(cause: DisconnectCause): Boolean {
+    fun setConnectionDisconnect(cause: DisconnectCause): CallControlResult {
         setDisconnected(cause)
         destroy()
-        return true
+        return CallControlResult.Success()
     }
 
     // TODO:: verify the CallEndpoint change was successful. tracking bug: b/283324578
     @Suppress("deprecation")
-    fun requestEndpointChange(callEndpoint: CallEndpointCompat): Boolean {
+    fun requestEndpointChange(callEndpoint: CallEndpointCompat): CallControlResult {
         return if (Build.VERSION.SDK_INT < VERSION_CODES.P) {
             Api26PlusImpl.setAudio(callEndpoint, this)
-            true
+            CallControlResult.Success()
         } else {
             Api28PlusImpl.setAudio(callEndpoint, this, mCachedBluetoothDevices)
         }
@@ -172,17 +173,17 @@
             callEndpoint: CallEndpointCompat,
             connection: CallSessionLegacy,
             btCache: ArrayList<BluetoothDevice>
-        ): Boolean {
+        ): CallControlResult {
             if (callEndpoint.type == CallEndpointCompat.TYPE_BLUETOOTH) {
                 val btDevice = getBluetoothDeviceFromEndpoint(btCache, callEndpoint)
                 if (btDevice != null) {
                     connection.requestBluetoothAudio(btDevice)
-                    return true
+                    return CallControlResult.Success()
                 }
-                return false
+                return CallControlResult.Error(CallException.ERROR_BLUETOOTH_DEVICE_IS_NULL)
             } else {
                 connection.setAudioRoute(EndpointUtils.mapTypeToRoute(callEndpoint.type))
-                return true
+                return CallControlResult.Success()
             }
         }
 
@@ -210,8 +211,10 @@
             return null
         }
 
-        fun bluetoothDeviceMatchesEndpoint(btDevice: BluetoothDevice, endpoint: CallEndpointCompat):
-            Boolean {
+        fun bluetoothDeviceMatchesEndpoint(
+            btDevice: BluetoothDevice,
+            endpoint: CallEndpointCompat
+        ): Boolean {
             return (btDevice.address?.equals(endpoint.mMackAddress) ?: false)
         }
     }
@@ -225,74 +228,106 @@
         CoroutineScope(coroutineContext).launch {
             // Note the slight deviation here where onAnswer does not put the call into an ACTIVE
             // state as it does in the platform. This behavior is intentional for this path.
-            val clientCanAnswer: Boolean = onAnswerCallback(videoState)
-            if (clientCanAnswer) {
+            try {
+                onAnswerCallback(videoState)
                 setActive()
                 setVideoState(videoState)
-            } else {
-                // Disconnect cause consistent with platform behavior
-                setDisconnected(DisconnectCause(DisconnectCause.REJECTED))
+            } catch (e: Exception) {
+                handleCallbackFailure(e)
             }
         }
     }
 
     override fun onUnhold() {
         CoroutineScope(coroutineContext).launch {
-            val clientCanUnhold = onSetActiveCallback()
-            if (clientCanUnhold) {
+            try {
+                onSetActiveCallback()
                 setActive()
+            } catch (e: Exception) {
+                handleCallbackFailure(e)
             }
         }
     }
 
     override fun onHold() {
         CoroutineScope(coroutineContext).launch {
-            val clientCanHold = onSetInactiveCallback()
-            if (clientCanHold) {
+            try {
+                onSetInactiveCallback()
                 setOnHold()
+            } catch (e: Exception) {
+                handleCallbackFailure(e)
             }
         }
     }
 
+    private fun handleCallbackFailure(e: Exception) {
+        setConnectionDisconnect(DisconnectCause(DisconnectCause.LOCAL))
+        blockingSessionExecution.complete(Unit)
+        throw e
+    }
+
     override fun onDisconnect() {
         CoroutineScope(coroutineContext).launch {
-            onDisconnectCallback(
-                DisconnectCause(DisconnectCause.LOCAL)
-            )
-            setDisconnected(DisconnectCause(DisconnectCause.LOCAL))
-            blockingSessionExecution.complete(Unit)
+            try {
+                onDisconnectCallback(
+                    DisconnectCause(DisconnectCause.LOCAL)
+                )
+            } catch (e: Exception) {
+                throw e
+            } finally {
+                setConnectionDisconnect(DisconnectCause(DisconnectCause.LOCAL))
+                blockingSessionExecution.complete(Unit)
+            }
         }
     }
 
     override fun onReject(rejectReason: Int) {
         CoroutineScope(coroutineContext).launch {
-            if (state == Call.STATE_RINGING) {
-                onDisconnectCallback(
-                    DisconnectCause(DisconnectCause.REJECTED)
-                )
+            try {
+                if (state == Call.STATE_RINGING) {
+                    onDisconnectCallback(
+                        DisconnectCause(DisconnectCause.REJECTED)
+                    )
+                }
+            } catch (e: Exception) {
+                throw e
+            } finally {
                 setDisconnected(DisconnectCause(DisconnectCause.REJECTED))
+                blockingSessionExecution.complete(Unit)
             }
         }
     }
 
     override fun onReject(rejectMessage: String) {
         CoroutineScope(coroutineContext).launch {
-            if (state == Call.STATE_RINGING) {
-                onDisconnectCallback(
-                    DisconnectCause(DisconnectCause.REJECTED)
-                )
+            try {
+                if (state == Call.STATE_RINGING) {
+                    onDisconnectCallback(
+                        DisconnectCause(DisconnectCause.REJECTED)
+                    )
+                }
+            } catch (e: Exception) {
+                throw e
+            } finally {
                 setDisconnected(DisconnectCause(DisconnectCause.REJECTED))
+                blockingSessionExecution.complete(Unit)
             }
         }
     }
 
     override fun onReject() {
         CoroutineScope(coroutineContext).launch {
-            if (state == Call.STATE_RINGING) {
-                onDisconnectCallback(
-                    DisconnectCause(DisconnectCause.REJECTED)
-                )
+            try {
+                if (state == Call.STATE_RINGING) {
+                    onDisconnectCallback(
+                        DisconnectCause(DisconnectCause.REJECTED)
+                    )
+                }
+            } catch (e: Exception) {
+                throw e
+            } finally {
                 setDisconnected(DisconnectCause(DisconnectCause.REJECTED))
+                blockingSessionExecution.complete(Unit)
             }
         }
     }
@@ -314,25 +349,26 @@
             return session.getCallId()
         }
 
-        override suspend fun setActive(): Boolean {
+        override suspend fun setActive(): CallControlResult {
             return session.setConnectionActive()
         }
 
-        override suspend fun setInactive(): Boolean {
+        override suspend fun setInactive(): CallControlResult {
             return session.setConnectionInactive()
         }
 
-        override suspend fun answer(callType: Int): Boolean {
+        override suspend fun answer(callType: Int): CallControlResult {
             return session.answer(callType)
         }
 
-        override suspend fun disconnect(disconnectCause: DisconnectCause): Boolean {
+        override suspend fun disconnect(disconnectCause: DisconnectCause): CallControlResult {
             val result = session.setConnectionDisconnect(disconnectCause)
             blockingSessionExecution.complete(Unit)
             return result
         }
 
-        override suspend fun requestEndpointChange(endpoint: CallEndpointCompat): Boolean {
+        override suspend fun requestEndpointChange(endpoint: CallEndpointCompat):
+            CallControlResult {
             return session.requestEndpointChange(endpoint)
         }
 
@@ -343,7 +379,6 @@
         override val availableEndpoints: Flow<List<CallEndpointCompat>> =
             callChannels.availableEndpointChannel.receiveAsFlow()
 
-        override val isMuted: Flow<Boolean> =
-            callChannels.isMutedChannel.receiveAsFlow()
+        override val isMuted: Flow<Boolean> = callChannels.isMutedChannel.receiveAsFlow()
     }
 }
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/internal/JetpackConnectionService.kt b/core/core-telecom/src/main/java/androidx/core/telecom/internal/JetpackConnectionService.kt
index 52dff47..e1fe957 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/internal/JetpackConnectionService.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/internal/JetpackConnectionService.kt
@@ -44,10 +44,10 @@
         val callChannel: CallChannels,
         val coroutineContext: CoroutineContext,
         val completableDeferred: CompletableDeferred<CallSessionLegacy>?,
-        val onAnswer: suspend (callType: Int) -> Boolean,
-        val onDisconnect: suspend (disconnectCause: DisconnectCause) -> Boolean,
-        val onSetActive: suspend () -> Boolean,
-        val onSetInactive: suspend () -> Boolean,
+        val onAnswer: suspend (callType: Int) -> Unit,
+        val onDisconnect: suspend (disconnectCause: DisconnectCause) -> Unit,
+        val onSetActive: suspend () -> Unit,
+        val onSetInactive: suspend () -> Unit,
         val execution: CompletableDeferred<Unit>
     )
 
diff --git a/core/core-telecom/src/main/java/androidx/core/telecom/internal/utils/Utils.kt b/core/core-telecom/src/main/java/androidx/core/telecom/internal/utils/Utils.kt
index 0d672bb..47121d6 100644
--- a/core/core-telecom/src/main/java/androidx/core/telecom/internal/utils/Utils.kt
+++ b/core/core-telecom/src/main/java/androidx/core/telecom/internal/utils/Utils.kt
@@ -26,7 +26,6 @@
 import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.core.telecom.CallAttributesCompat
-import androidx.core.telecom.CallException
 import androidx.core.telecom.CallsManager
 
 internal class Utils {
@@ -72,7 +71,9 @@
 
         fun verifyBuildVersion() {
             if (mBuildVersion.hasInvalidBuildVersion()) {
-                throw UnsupportedOperationException(CallException.ERROR_BUILD_VERSION_MSG)
+                throw UnsupportedOperationException("Core-Telecom only supports builds from" +
+                    " Oreo (Android 8) and above.  In order to utilize Core-Telecom, your device" +
+                    " must be updated.")
             }
         }
 
diff --git a/credentials/credentials-play-services-auth/build.gradle b/credentials/credentials-play-services-auth/build.gradle
index 890c5d9..3b5972d 100644
--- a/credentials/credentials-play-services-auth/build.gradle
+++ b/credentials/credentials-play-services-auth/build.gradle
@@ -26,7 +26,7 @@
     api(libs.kotlinStdlib)
     api project(":credentials:credentials")
 
-    implementation("com.google.android.libraries.identity.googleid:googleid:1.0.0"){
+    implementation("com.google.android.libraries.identity.googleid:googleid:1.1.0"){
         exclude group: "androidx.credentials"
     }
 
diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerTest.kt b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerTest.kt
index d245c4b..a387feb 100644
--- a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerTest.kt
+++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/beginsignin/CredentialProviderBeginSignInControllerTest.kt
@@ -124,7 +124,6 @@
             TestCredentialsActivity::class.java
         )
         activityScenario.onActivity { activity: TestCredentialsActivity? ->
-
             val firstInstance = getInstance(activity!!)
             val secondInstance = getInstance(activity)
             assertThat(firstInstance).isEqualTo(secondInstance)
diff --git a/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/getsigninintent/CredentialProviderGetSignInIntentControllerTest.kt b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/getsigninintent/CredentialProviderGetSignInIntentControllerTest.kt
new file mode 100644
index 0000000..d045cad
--- /dev/null
+++ b/credentials/credentials-play-services-auth/src/androidTest/java/androidx/credentials/playservices/getsigninintent/CredentialProviderGetSignInIntentControllerTest.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.credentials.playservices.getsigninintent
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.credentials.GetCredentialRequest
+import androidx.credentials.GetPasswordOption
+import androidx.credentials.exceptions.GetCredentialUnsupportedException
+import androidx.credentials.playservices.TestCredentialsActivity
+import androidx.credentials.playservices.controllers.GetSignInIntent.CredentialProviderGetSignInIntentController.Companion.getInstance
+import androidx.test.core.app.ActivityScenario
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.android.gms.auth.api.identity.GetSignInIntentRequest
+import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@Suppress("deprecation")
+@RequiresApi(api = Build.VERSION_CODES.O)
+class CredentialProviderGetSignInIntentControllerTest {
+
+    @Test
+    fun convertRequestToPlayServices_success() {
+        val serverClientId: String = "server_client_id"
+        val activityScenario = ActivityScenario.launch(
+            TestCredentialsActivity::class.java
+        )
+        activityScenario.onActivity { activity: TestCredentialsActivity? ->
+            val actual: GetSignInIntentRequest = getInstance(activity!!)
+                .convertRequestToPlayServices(
+                    GetCredentialRequest(
+                        listOf(
+                            GetSignInWithGoogleOption.Builder(serverClientId).build()
+                        )
+                    )
+                )
+            assertThat(
+                actual.serverClientId
+            ).isEqualTo(serverClientId)
+        }
+    }
+
+    @Test
+    fun convertRequestToPlayServices_moreThanOneOption_failure() {
+        val serverClientId: String = "server_client_id"
+        val activityScenario = ActivityScenario.launch(
+            TestCredentialsActivity::class.java
+        )
+        activityScenario.onActivity { activity: TestCredentialsActivity? ->
+            assertThrows(GetCredentialUnsupportedException::class.java) {
+                getInstance(activity!!)
+                    .convertRequestToPlayServices(
+                        GetCredentialRequest(
+                            listOf(
+                                GetPasswordOption(),
+                                GetSignInWithGoogleOption.Builder(serverClientId).build()
+                            )
+                        )
+                    )
+            }
+        }
+    }
+}
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/CredentialProviderPlayServicesImpl.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/CredentialProviderPlayServicesImpl.kt
index f65e129..def1c27 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/CredentialProviderPlayServicesImpl.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/CredentialProviderPlayServicesImpl.kt
@@ -37,9 +37,11 @@
 import androidx.credentials.playservices.controllers.BeginSignIn.CredentialProviderBeginSignInController
 import androidx.credentials.playservices.controllers.CreatePassword.CredentialProviderCreatePasswordController
 import androidx.credentials.playservices.controllers.CreatePublicKeyCredential.CredentialProviderCreatePublicKeyCredentialController
+import androidx.credentials.playservices.controllers.GetSignInIntent.CredentialProviderGetSignInIntentController
 import com.google.android.gms.auth.api.identity.Identity
 import com.google.android.gms.common.ConnectionResult
 import com.google.android.gms.common.GoogleApiAvailability
+import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption
 import java.util.concurrent.Executor
 
 /**
@@ -60,8 +62,15 @@
         callback: CredentialManagerCallback<GetCredentialResponse, GetCredentialException>
     ) {
         if (cancellationReviewer(cancellationSignal)) { return }
-        CredentialProviderBeginSignInController(context).invokePlayServices(
-            request, callback, executor, cancellationSignal)
+        if (isGetSignInIntentRequest(request)) {
+            CredentialProviderGetSignInIntentController(context).invokePlayServices(
+                request, callback, executor, cancellationSignal
+            )
+        } else {
+            CredentialProviderBeginSignInController(context).invokePlayServices(
+                request, callback, executor, cancellationSignal
+            )
+        }
     }
 
     @SuppressWarnings("deprecated")
@@ -162,5 +171,14 @@
             }
             return false
         }
+
+        internal fun isGetSignInIntentRequest(request: GetCredentialRequest): Boolean {
+            for (option in request.credentialOptions) {
+                if (option is GetSignInWithGoogleOption) {
+                    return true
+                }
+            }
+            return false
+        }
     }
 }
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/HiddenActivity.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/HiddenActivity.kt
index bde5471..0e21647 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/HiddenActivity.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/HiddenActivity.kt
@@ -31,6 +31,7 @@
 import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.GET_NO_CREDENTIALS
 import androidx.credentials.playservices.controllers.CredentialProviderBaseController.Companion.GET_UNKNOWN
 import com.google.android.gms.auth.api.identity.BeginSignInRequest
+import com.google.android.gms.auth.api.identity.GetSignInIntentRequest
 import com.google.android.gms.auth.api.identity.Identity
 import com.google.android.gms.auth.api.identity.SavePasswordRequest
 import com.google.android.gms.common.api.ApiException
@@ -72,6 +73,9 @@
             }
             CredentialProviderBaseController.CREATE_PUBLIC_KEY_CREDENTIAL_TAG -> {
                 handleCreatePublicKeyCredential()
+            }
+            CredentialProviderBaseController.SIGN_IN_INTENT_TAG -> {
+                handleGetSignInIntent()
             } else -> {
                 Log.w(TAG, "Activity handed an unsupported type")
                 finish()
@@ -144,6 +148,47 @@
         super.onSaveInstanceState(outState)
     }
 
+    private fun handleGetSignInIntent() {
+        val params: GetSignInIntentRequest? = intent.getParcelableExtra(
+            CredentialProviderBaseController.REQUEST_TAG)
+        val requestCode: Int = intent.getIntExtra(
+            CredentialProviderBaseController.ACTIVITY_REQUEST_CODE_TAG,
+            DEFAULT_VALUE)
+        params?.let {
+            Identity.getSignInClient(this).getSignInIntent(params).addOnSuccessListener {
+                try {
+                    mWaitingForActivityResult = true
+                    startIntentSenderForResult(
+                        it.intentSender,
+                        requestCode,
+                        null,
+                        0,
+                        0,
+                        0,
+                        null
+                    )
+                } catch (e: IntentSender.SendIntentException) {
+                    setupFailure(resultReceiver!!,
+                        GET_UNKNOWN,
+                        "During get sign-in intent, one tap ui intent sender " +
+                            "failure: ${e.message}")
+                }
+            }.addOnFailureListener { e: Exception ->
+                var errName: String = GET_NO_CREDENTIALS
+                if (e is ApiException && e.statusCode in
+                    CredentialProviderBaseController.retryables) {
+                    errName = GET_INTERRUPTED
+                }
+                setupFailure(resultReceiver!!, errName,
+                    "During get sign-in intent, failure response from one tap: ${e.message}")
+            }
+        } ?: run {
+            Log.i(TAG, "During get sign-in intent, params is null, nothing to launch for " +
+                "get sign-in intent")
+            finish()
+        }
+    }
+
     private fun handleBeginSignIn() {
         val params: BeginSignInRequest? = intent.getParcelableExtra(
             CredentialProviderBaseController.REQUEST_TAG)
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CredentialProviderBaseController.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CredentialProviderBaseController.kt
index 3040f9d..c744990 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CredentialProviderBaseController.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/CredentialProviderBaseController.kt
@@ -67,6 +67,9 @@
         // Value for the specific begin sign in type
         const val BEGIN_SIGN_IN_TAG = "BEGIN_SIGN_IN"
 
+        // Key for the Sign-in Intent flow
+        const val SIGN_IN_INTENT_TAG = "SIGN_IN_INTENT"
+
         // Value for the specific create password type
         const val CREATE_PASSWORD_TAG = "CREATE_PASSWORD"
 
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/GetSignInIntent/CredentialProviderGetSignInIntentController.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/GetSignInIntent/CredentialProviderGetSignInIntentController.kt
new file mode 100644
index 0000000..41a9fed7
--- /dev/null
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/GetSignInIntent/CredentialProviderGetSignInIntentController.kt
@@ -0,0 +1,285 @@
+/*
+ * 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.credentials.playservices.controllers.GetSignInIntent
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.os.CancellationSignal
+import android.os.Handler
+import android.os.Looper
+import android.os.ResultReceiver
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import androidx.credentials.Credential
+import androidx.credentials.CredentialManagerCallback
+import androidx.credentials.GetCredentialRequest
+import androidx.credentials.GetCredentialResponse
+import androidx.credentials.exceptions.GetCredentialCancellationException
+import androidx.credentials.exceptions.GetCredentialException
+import androidx.credentials.exceptions.GetCredentialInterruptedException
+import androidx.credentials.exceptions.GetCredentialUnknownException
+import androidx.credentials.exceptions.GetCredentialUnsupportedException
+import androidx.credentials.playservices.CredentialProviderPlayServicesImpl
+import androidx.credentials.playservices.HiddenActivity
+import androidx.credentials.playservices.controllers.CredentialProviderBaseController
+import androidx.credentials.playservices.controllers.CredentialProviderController
+import com.google.android.gms.auth.api.identity.GetSignInIntentRequest
+import com.google.android.gms.auth.api.identity.Identity
+import com.google.android.gms.auth.api.identity.SignInCredential
+import com.google.android.gms.common.api.ApiException
+import com.google.android.gms.common.api.CommonStatusCodes
+import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption
+import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
+import java.util.concurrent.Executor
+
+/**
+ * A controller to handle the GetSignInIntent flow with play services.
+ */
+@Suppress("deprecation")
+internal class CredentialProviderGetSignInIntentController(private val context: Context) :
+    CredentialProviderController<GetCredentialRequest, GetSignInIntentRequest,
+        SignInCredential, GetCredentialResponse, GetCredentialException>(context) {
+
+    /**
+     * The callback object state, used in the protected handleResponse method.
+     */
+    @VisibleForTesting
+    lateinit var callback: CredentialManagerCallback<GetCredentialResponse, GetCredentialException>
+
+    /**
+     * The callback requires an executor to invoke it.
+     */
+    @VisibleForTesting
+    lateinit var executor: Executor
+
+    /**
+     * The cancellation signal, which is shuttled around to stop the flow at any moment prior to
+     * returning data.
+     */
+    @VisibleForTesting
+    private var cancellationSignal: CancellationSignal? = null
+
+    private val resultReceiver = object : ResultReceiver(
+        Handler(Looper.getMainLooper())
+    ) {
+        public override fun onReceiveResult(
+            resultCode: Int,
+            resultData: Bundle
+        ) {
+            if (maybeReportErrorFromResultReceiver(
+                    resultData,
+                 CredentialProviderBaseController.Companion::getCredentialExceptionTypeToException,
+                    executor = executor,
+                    callback = callback,
+                    cancellationSignal
+                )
+            ) return
+            handleResponse(
+                resultData.getInt(ACTIVITY_REQUEST_CODE_TAG),
+                resultCode,
+                resultData.getParcelable(RESULT_DATA_TAG)
+            )
+        }
+    }
+
+    override fun invokePlayServices(
+        request: GetCredentialRequest,
+        callback: CredentialManagerCallback<GetCredentialResponse, GetCredentialException>,
+        executor: Executor,
+        cancellationSignal: CancellationSignal?
+    ) {
+        this.cancellationSignal = cancellationSignal
+        this.callback = callback
+        this.executor = executor
+
+        if (CredentialProviderPlayServicesImpl.cancellationReviewer(cancellationSignal)) {
+            return
+        }
+
+        try {
+            val convertedRequest: GetSignInIntentRequest =
+                this.convertRequestToPlayServices(request)
+
+            val hiddenIntent = Intent(context, HiddenActivity::class.java)
+            hiddenIntent.putExtra(REQUEST_TAG, convertedRequest)
+            generateHiddenActivityIntent(resultReceiver, hiddenIntent, SIGN_IN_INTENT_TAG)
+            context.startActivity(hiddenIntent)
+        } catch (e: Exception) {
+            when (e) {
+                is GetCredentialUnsupportedException ->
+                    cancelOrCallbackExceptionOrResult(cancellationSignal) {
+                        this.executor.execute {
+                            this.callback.onError(e)
+                        }
+                    }
+                else ->
+                    cancelOrCallbackExceptionOrResult(cancellationSignal) {
+                        this.executor.execute {
+                            this.callback.onError(
+                                GetCredentialUnknownException(ERROR_MESSAGE_START_ACTIVITY_FAILED)
+                            )
+                        }
+                    }
+            }
+        }
+    }
+
+    @VisibleForTesting
+    public override fun convertRequestToPlayServices(request: GetCredentialRequest):
+        GetSignInIntentRequest {
+        if (request.credentialOptions.count() != 1) {
+            throw GetCredentialUnsupportedException(
+                "GetSignInWithGoogleOption cannot be combined with other options."
+            )
+        }
+        val option = request.credentialOptions[0] as GetSignInWithGoogleOption
+        return GetSignInIntentRequest.builder()
+            .setServerClientId(option.serverClientId)
+            .filterByHostedDomain(option.hostedDomainFilter)
+            .setNonce(option.nonce)
+            .build()
+    }
+
+    override fun convertResponseToCredentialManager(response: SignInCredential):
+        GetCredentialResponse {
+        var cred: Credential? = null
+        if (response.googleIdToken != null) {
+            cred = createGoogleIdCredential(response)
+        } else {
+            Log.w(TAG, "Credential returned but no google Id found")
+        }
+        if (cred == null) {
+            throw GetCredentialUnknownException(
+                "When attempting to convert get response, " + "null credential found"
+            )
+        }
+        return GetCredentialResponse(cred)
+    }
+
+    @VisibleForTesting
+    fun createGoogleIdCredential(response: SignInCredential): GoogleIdTokenCredential {
+        var cred = GoogleIdTokenCredential.Builder().setId(response.id)
+        try {
+            cred.setIdToken(response.googleIdToken!!)
+        } catch (e: Exception) {
+            throw GetCredentialUnknownException(
+                "When attempting to convert get response, " + "null Google ID Token found"
+            )
+        }
+
+        if (response.displayName != null) {
+            cred.setDisplayName(response.displayName)
+        }
+
+        if (response.givenName != null) {
+            cred.setGivenName(response.givenName)
+        }
+
+        if (response.familyName != null) {
+            cred.setFamilyName(response.familyName)
+        }
+
+        if (response.phoneNumber != null) {
+            cred.setPhoneNumber(response.phoneNumber)
+        }
+
+        if (response.profilePictureUri != null) {
+            cred.setProfilePictureUri(response.profilePictureUri)
+        }
+
+        return cred.build()
+    }
+
+    internal fun handleResponse(uniqueRequestCode: Int, resultCode: Int, data: Intent?) {
+        if (uniqueRequestCode != CONTROLLER_REQUEST_CODE) {
+            Log.w(
+                TAG,
+                "Returned request code $CONTROLLER_REQUEST_CODE which " +
+                    " does not match what was given $uniqueRequestCode"
+            )
+            return
+        }
+        if (maybeReportErrorResultCodeGet(
+                resultCode,
+                { s, f -> cancelOrCallbackExceptionOrResult(s, f) },
+                { e ->
+                    this.executor.execute {
+                        this.callback.onError(e)
+                    }
+                },
+                cancellationSignal
+            )
+        ) return
+        try {
+            val signInCredential =
+                Identity.getSignInClient(context).getSignInCredentialFromIntent(data)
+            val response = convertResponseToCredentialManager(signInCredential)
+            cancelOrCallbackExceptionOrResult(cancellationSignal) {
+                this.executor.execute {
+                    this.callback.onResult(response)
+                }
+            }
+        } catch (e: ApiException) {
+            var exception: GetCredentialException = GetCredentialUnknownException(e.message)
+            if (e.statusCode == CommonStatusCodes.CANCELED) {
+                exception = GetCredentialCancellationException(e.message)
+            } else if (e.statusCode in retryables) {
+                exception = GetCredentialInterruptedException(e.message)
+            }
+            cancelOrCallbackExceptionOrResult(cancellationSignal) {
+                executor.execute {
+                    callback.onError(exception)
+                }
+            }
+            return
+        } catch (e: GetCredentialException) {
+            cancelOrCallbackExceptionOrResult(cancellationSignal) {
+                executor.execute {
+                    callback.onError(e)
+                }
+            }
+        } catch (t: Throwable) {
+            val e = GetCredentialUnknownException(t.message)
+            cancelOrCallbackExceptionOrResult(cancellationSignal) {
+                executor.execute {
+                    callback.onError(e)
+                }
+            }
+        }
+    }
+
+    companion object {
+        private const val TAG = "GetSignInIntent"
+        private var controller: CredentialProviderGetSignInIntentController? = null
+
+        /**
+         * This finds a past version of the [CredentialProviderGetSignInIntentController] if it exists,
+         * otherwise it generates a new instance.
+         *
+         * @param context the calling context for this controller
+         * @return a credential provider controller for a specific begin sign in credential request
+         */
+        @JvmStatic
+        fun getInstance(context: Context): CredentialProviderGetSignInIntentController {
+            if (controller == null) {
+                controller = CredentialProviderGetSignInIntentController(context)
+            }
+            return controller!!
+        }
+    }
+}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/PendingIntentHandlerJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/PendingIntentHandlerJavaTest.java
index b909602..5b332ac 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/PendingIntentHandlerJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/PendingIntentHandlerJavaTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.content.Intent;
+import android.os.Build;
 
 import androidx.annotation.RequiresApi;
 import androidx.credentials.CreatePasswordResponse;
@@ -42,6 +43,10 @@
 
     @Test
     public void test_setGetCreateCredentialException() {
+        if (Build.VERSION.SDK_INT >= 34) {
+            return;
+        }
+
         Intent intent = new Intent();
 
         CreateCredentialInterruptedException initialException =
@@ -57,6 +62,10 @@
 
     @Test
     public void test_setGetCreateCredentialException_throwsWhenEmptyIntent() {
+        if (Build.VERSION.SDK_INT >= 34) {
+            return;
+        }
+
         assertThat(
                         IntentHandlerConverters.getCreateCredentialException(
                                 BLANK_INTENT))
@@ -65,6 +74,10 @@
 
     @Test
     public void test_credentialException() {
+        if (Build.VERSION.SDK_INT >= 34) {
+            return;
+        }
+
         Intent intent = new Intent();
         GetCredentialInterruptedException initialException =
                 new GetCredentialInterruptedException("message");
@@ -79,12 +92,19 @@
 
     @Test
     public void test_credentialException_throwsWhenEmptyIntent() {
+        if (Build.VERSION.SDK_INT >= 34) {
+            return;
+        }
+
         assertThat(IntentHandlerConverters.getGetCredentialException(BLANK_INTENT))
                 .isNull();
     }
 
     @Test
     public void test_beginGetResponse() {
+        if (Build.VERSION.SDK_INT >= 34) {
+            return;
+        }
 
         Intent intent = new Intent();
         BeginGetCredentialResponse initialResponse =
@@ -100,12 +120,20 @@
 
     @Test
     public void test_beginGetResponse_throwsWhenEmptyIntent() {
+        if (Build.VERSION.SDK_INT >= 34) {
+            return;
+        }
+
         assertThat(IntentHandlerConverters.getBeginGetResponse(BLANK_INTENT))
                 .isNull();
     }
 
     @Test
     public void test_credentialResponse() {
+        if (Build.VERSION.SDK_INT >= 34) {
+            return;
+        }
+
         Intent intent = new Intent();
         PasswordCredential credential = new PasswordCredential("a", "b");
         GetCredentialResponse initialResponse = new GetCredentialResponse(credential);
@@ -120,12 +148,20 @@
 
     @Test
     public void test_credentialResponse_throwsWhenEmptyIntent() {
+        if (Build.VERSION.SDK_INT >= 34) {
+            return;
+        }
+
         assertThat(IntentHandlerConverters.getGetCredentialResponse(BLANK_INTENT))
                 .isNull();
     }
 
     @Test
     public void test_createCredentialCredentialResponse() {
+        if (Build.VERSION.SDK_INT >= 34) {
+            return;
+        }
+
         Intent intent = new Intent();
         CreatePasswordResponse initialResponse = new CreatePasswordResponse();
 
@@ -140,6 +176,10 @@
 
     @Test
     public void test_createCredentialCredentialResponse_throwsWhenEmptyIntent() {
+        if (Build.VERSION.SDK_INT >= 34) {
+            return;
+        }
+
         assertThat(
                         IntentHandlerConverters
                                 .getCreateCredentialCredentialResponse(BLANK_INTENT))
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/PendingIntentHandlerTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/PendingIntentHandlerTest.kt
index 7e9e1a2..c9ed499 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/provider/PendingIntentHandlerTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/provider/PendingIntentHandlerTest.kt
@@ -16,6 +16,7 @@
 package androidx.credentials.provider
 
 import android.content.Intent
+import android.os.Build
 import androidx.annotation.RequiresApi
 import androidx.credentials.CreatePasswordResponse
 import androidx.credentials.GetCredentialResponse
@@ -36,6 +37,10 @@
 class PendingIntentHandlerTest {
     @Test
     fun test_createCredentialException() {
+        if (Build.VERSION.SDK_INT >= 34) {
+            return
+        }
+
         val intent = Intent()
         val initialException = CreateCredentialInterruptedException("message")
 
@@ -48,12 +53,20 @@
 
     @Test()
     fun test_createCredentialException_throwsWhenEmptyIntent() {
+        if (Build.VERSION.SDK_INT >= 34) {
+            return
+        }
+
         val intent = Intent()
         assertThat(intent.getCreateCredentialException()).isNull()
     }
 
     @Test
     fun test_credentialException() {
+        if (Build.VERSION.SDK_INT >= 34) {
+            return
+        }
+
         val intent = Intent()
         val initialException = GetCredentialInterruptedException("message")
 
@@ -66,12 +79,20 @@
 
     @Test
     fun test_credentialException_throwsWhenEmptyIntent() {
+        if (Build.VERSION.SDK_INT >= 34) {
+            return
+        }
+
         val intent = Intent()
         assertThat(intent.getGetCredentialException()).isNull()
     }
 
     @Test
     fun test_beginGetResponse() {
+        if (Build.VERSION.SDK_INT >= 34) {
+            return
+        }
+
         val intent = Intent()
         val initialResponse = BeginGetCredentialResponse.Builder().build()
 
@@ -84,12 +105,20 @@
 
     @Test
     fun test_beginGetResponse_throwsWhenEmptyIntent() {
+        if (Build.VERSION.SDK_INT >= 34) {
+            return
+        }
+
         val intent = Intent()
         assertThat(intent.getBeginGetResponse()).isNull()
     }
 
     @Test
     fun test_credentialResponse() {
+        if (Build.VERSION.SDK_INT >= 34) {
+            return
+        }
+
         val intent = Intent()
         val credential = PasswordCredential("a", "b")
         val initialResponse = GetCredentialResponse(credential)
@@ -103,12 +132,20 @@
 
     @Test
     fun test_credentialResponse_throwsWhenEmptyIntent() {
+        if (Build.VERSION.SDK_INT >= 34) {
+            return
+        }
+
         val intent = Intent()
         assertThat(intent.getGetCredentialResponse()).isNull()
     }
 
     @Test
     fun test_createCredentialCredentialResponse() {
+        if (Build.VERSION.SDK_INT >= 34) {
+            return
+        }
+
         val intent = Intent()
         val initialResponse = CreatePasswordResponse()
 
@@ -121,6 +158,10 @@
 
     @Test
     fun test_createCredentialCredentialResponse_throwsWhenEmptyIntent() {
+        if (Build.VERSION.SDK_INT >= 34) {
+            return
+        }
+
         val intent = Intent()
         val r = intent.getCreateCredentialCredentialResponse()
         assertThat(r).isNull()
diff --git a/datastore/datastore-core/build.gradle b/datastore/datastore-core/build.gradle
index e71978e..8feb7e2 100644
--- a/datastore/datastore-core/build.gradle
+++ b/datastore/datastore-core/build.gradle
@@ -19,6 +19,7 @@
 import androidx.build.PlatformIdentifier
 import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
 import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
+import com.google.protobuf.gradle.ProtobufExtract
 
 plugins {
     id("AndroidXPlugin")
@@ -57,6 +58,11 @@
     }
 }
 
+def protoDir = project.layout.projectDirectory.dir("src/androidInstrumentedTest/proto")
+tasks.named("extractAndroidTestProto").configure {
+    it.inputFiles.from(project.files(protoDir))
+}
+
 androidXMultiplatform {
     jvm()
     mac()
@@ -114,6 +120,13 @@
             dependsOn(jvmTest)
             dependencies {
                 implementation(libs.protobufLite)
+            }
+        }
+
+        androidInstrumentedTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(libs.protobufLite)
                 implementation(libs.truth)
                 implementation(project(":internal-testutils-truth"))
                 implementation(libs.testRunner)
diff --git a/datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/MultiProcessDataStoreFactoryTest.kt b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/MultiProcessDataStoreFactoryTest.kt
similarity index 100%
rename from datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/MultiProcessDataStoreFactoryTest.kt
rename to datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/MultiProcessDataStoreFactoryTest.kt
diff --git a/datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/MultiProcessDataStoreSingleProcessFileTest.kt b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/MultiProcessDataStoreSingleProcessFileTest.kt
similarity index 100%
rename from datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/MultiProcessDataStoreSingleProcessFileTest.kt
rename to datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/MultiProcessDataStoreSingleProcessFileTest.kt
diff --git a/datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/MultiProcessDataStoreSingleProcessOkioTest.kt b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/MultiProcessDataStoreSingleProcessOkioTest.kt
similarity index 100%
rename from datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/MultiProcessDataStoreSingleProcessOkioTest.kt
rename to datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/MultiProcessDataStoreSingleProcessOkioTest.kt
diff --git a/datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/MultiProcessDataStoreSingleProcessTest.kt b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/MultiProcessDataStoreSingleProcessTest.kt
similarity index 98%
rename from datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/MultiProcessDataStoreSingleProcessTest.kt
rename to datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/MultiProcessDataStoreSingleProcessTest.kt
index 952fc5d..24bd9db 100644
--- a/datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/MultiProcessDataStoreSingleProcessTest.kt
+++ b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/MultiProcessDataStoreSingleProcessTest.kt
@@ -56,6 +56,7 @@
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
 import kotlinx.coroutines.withContext
+import kotlinx.coroutines.withTimeout
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -822,7 +823,7 @@
     }
 
     @Test
-    fun testCancelInflightWrite() = runBlocking<Unit> {
+    fun testCancelInflightWrite() = doBlockingWithTimeout(1000) {
         val myScope =
             CoroutineScope(Job() + Executors.newSingleThreadExecutor().asCoroutineDispatcher())
 
@@ -838,7 +839,7 @@
     }
 
     @Test
-    fun testWrite_afterCanceledWrite_succeeds() = runBlocking<Unit> {
+    fun testWrite_afterCanceledWrite_succeeds() = doBlockingWithTimeout(1000) {
         val myScope =
             CoroutineScope(Job() + Executors.newSingleThreadExecutor().asCoroutineDispatcher())
 
@@ -858,8 +859,8 @@
     }
 
     @Test
-    fun testWrite_fromOtherScope_doesntGetCancelledFromDifferentScope() = runBlocking<Unit> {
-
+    fun testWrite_fromOtherScope_doesntGetCancelledFromDifferentScope() =
+    doBlockingWithTimeout(1000) {
         val otherScope = CoroutineScope(Job())
 
         val callerScope = CoroutineScope(Job())
@@ -981,3 +982,7 @@
         }
     }
 }
+
+fun doBlockingWithTimeout(ms: Long, block: suspend () -> Unit): Unit = runBlocking<Unit> {
+    withTimeout(ms) { block() }
+}
diff --git a/datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/MulticastFileObserverTest.kt b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/MulticastFileObserverTest.kt
similarity index 100%
rename from datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/MulticastFileObserverTest.kt
rename to datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/MulticastFileObserverTest.kt
diff --git a/datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/ProtoOkioSerializer.kt b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/ProtoOkioSerializer.kt
similarity index 100%
rename from datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/ProtoOkioSerializer.kt
rename to datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/ProtoOkioSerializer.kt
diff --git a/datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/ProtoSerializer.kt b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/ProtoSerializer.kt
similarity index 100%
rename from datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/ProtoSerializer.kt
rename to datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/ProtoSerializer.kt
diff --git a/datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/SharedCounterTest.kt b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/SharedCounterTest.kt
similarity index 100%
rename from datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/SharedCounterTest.kt
rename to datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/SharedCounterTest.kt
diff --git a/datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/multiprocess/InterProcessCompletableTest.kt b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/InterProcessCompletableTest.kt
similarity index 100%
rename from datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/multiprocess/InterProcessCompletableTest.kt
rename to datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/InterProcessCompletableTest.kt
diff --git a/datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/multiprocess/MultiProcessDataStoreIpcTest.kt b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/MultiProcessDataStoreIpcTest.kt
similarity index 100%
rename from datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/multiprocess/MultiProcessDataStoreIpcTest.kt
rename to datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/MultiProcessDataStoreIpcTest.kt
diff --git a/datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/multiprocess/MultiProcessTestRule.kt b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/MultiProcessTestRule.kt
similarity index 100%
rename from datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/multiprocess/MultiProcessTestRule.kt
rename to datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/MultiProcessTestRule.kt
diff --git a/datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/multiprocess/MultipleDataStoresInMultipleProcessesTest.kt b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/MultipleDataStoresInMultipleProcessesTest.kt
similarity index 100%
rename from datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/multiprocess/MultipleDataStoresInMultipleProcessesTest.kt
rename to datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/MultipleDataStoresInMultipleProcessesTest.kt
diff --git a/datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/multiprocess/TwoWayIpcTest.kt b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/TwoWayIpcTest.kt
similarity index 100%
rename from datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/multiprocess/TwoWayIpcTest.kt
rename to datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/TwoWayIpcTest.kt
diff --git a/datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/multiprocess/ipcActions/CreateDatastoreAction.kt b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/ipcActions/CreateDatastoreAction.kt
similarity index 100%
rename from datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/multiprocess/ipcActions/CreateDatastoreAction.kt
rename to datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/ipcActions/CreateDatastoreAction.kt
diff --git a/datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/multiprocess/ipcActions/ReadTextAction.kt b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/ipcActions/ReadTextAction.kt
similarity index 100%
rename from datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/multiprocess/ipcActions/ReadTextAction.kt
rename to datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/ipcActions/ReadTextAction.kt
diff --git a/datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/multiprocess/ipcActions/SetTextAction.kt b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/ipcActions/SetTextAction.kt
similarity index 100%
rename from datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/multiprocess/ipcActions/SetTextAction.kt
rename to datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/multiprocess/ipcActions/SetTextAction.kt
diff --git a/datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/twoWayIpc/CompositeServiceSubjectModel.kt b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/CompositeServiceSubjectModel.kt
similarity index 100%
rename from datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/twoWayIpc/CompositeServiceSubjectModel.kt
rename to datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/CompositeServiceSubjectModel.kt
diff --git a/datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/twoWayIpc/InterProcessCompletable.kt b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/InterProcessCompletable.kt
similarity index 100%
rename from datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/twoWayIpc/InterProcessCompletable.kt
rename to datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/InterProcessCompletable.kt
diff --git a/datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/twoWayIpc/IpcAction.kt b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/IpcAction.kt
similarity index 100%
rename from datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/twoWayIpc/IpcAction.kt
rename to datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/IpcAction.kt
diff --git a/datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/twoWayIpc/IpcLogger.kt b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/IpcLogger.kt
similarity index 100%
rename from datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/twoWayIpc/IpcLogger.kt
rename to datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/IpcLogger.kt
diff --git a/datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/twoWayIpc/TwoWayIpcBus.kt b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/TwoWayIpcBus.kt
similarity index 100%
rename from datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/twoWayIpc/TwoWayIpcBus.kt
rename to datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/TwoWayIpcBus.kt
diff --git a/datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/twoWayIpc/TwoWayIpcConnection.kt b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/TwoWayIpcConnection.kt
similarity index 100%
rename from datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/twoWayIpc/TwoWayIpcConnection.kt
rename to datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/TwoWayIpcConnection.kt
diff --git a/datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/twoWayIpc/TwoWayIpcService.kt b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/TwoWayIpcService.kt
similarity index 100%
rename from datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/twoWayIpc/TwoWayIpcService.kt
rename to datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/TwoWayIpcService.kt
diff --git a/datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/twoWayIpc/TwoWayIpcSubject.kt b/datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/TwoWayIpcSubject.kt
similarity index 100%
rename from datastore/datastore-core/src/androidUnitTest/java/androidx/datastore/core/twoWayIpc/TwoWayIpcSubject.kt
rename to datastore/datastore-core/src/androidInstrumentedTest/kotlin/androidx/datastore/core/twoWayIpc/TwoWayIpcSubject.kt
diff --git a/datastore/datastore-core/src/androidUnitTest/proto/test.proto b/datastore/datastore-core/src/androidInstrumentedTest/proto/test.proto
similarity index 100%
rename from datastore/datastore-core/src/androidUnitTest/proto/test.proto
rename to datastore/datastore-core/src/androidInstrumentedTest/proto/test.proto
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 9e69f20..0f56a50 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -741,3 +741,11 @@
 # > 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;
+# b/300090636
+Could not load custom lint check jar file \$GRADLE_USER_HOME/caches/transforms\-[0-9]+/[0-9a-f]{32}/transformed/material\-[0-9]+\.[0-9]+\.[0-9]+/jars/lint\.jar
+java\.lang\.ClassCastException: class java\.util\.HashMap\$Node cannot be cast to class java\.util\.HashMap\$TreeNode \(java\.util\.HashMap\$Node and java\.util\.HashMap\$TreeNode are in module java\.base of loader 'bootstrap'\)
+at com\.android\.tools\.lint\.client\.api\.JarFileIssueRegistry\$Factory\.get\(JarFileIssueRegistry\.kt:[0-9]+\)
+at com\.android\.tools\.lint\.client\.api\.JarFileIssueRegistry\$Factory\.get\$default\(JarFileIssueRegistry\.kt:[0-9]+\)
+at com\.android\.tools\.lint\.client\.api\.LintDriver\.registerCustomDetectors\(LintDriver\.kt:[0-9]+\)
+at com\.android\.tools\.lint\.client\.api\.LintDriver\.initializeExtraRegistries\(LintDriver\.kt:[0-9]+\)
+at jdk\.internal\.reflect\.GeneratedMethodAccessor[0-9]+\.invoke\(Unknown Source\)
diff --git a/development/update_studio.sh b/development/update_studio.sh
index 2239505..3bdab1e 100755
--- a/development/update_studio.sh
+++ b/development/update_studio.sh
@@ -5,10 +5,12 @@
   eval "$@"
 }
 
-# Get versions
+# Versions that the user should update when running this script
 echo Getting Studio version and link
-AGP_VERSION=${1:-8.2.0-beta01}
-STUDIO_VERSION_STRING=${2:-"Android Studio Hedgehog | 2023.1.1 Beta 1"}
+AGP_VERSION=${1:-8.3.0-alpha02}
+STUDIO_VERSION_STRING=${2:-"Android Studio Iguana | 2023.2.1 Canary 2"}
+
+# Get studio version number from version name
 STUDIO_IFRAME_LINK=`curl "https://developer.android.com/studio/archive.html" | grep "<iframe " | sed "s/.* src=\"\([^\"]*\)\".*/\1/g"`
 echo iframe link $STUDIO_IFRAME_LINK
 STUDIO_IFRAME_REDIRECT=`curl -s $STUDIO_IFRAME_LINK | grep href | sed 's/.*href="\([^"]*\)".*/\1/g'`
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/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/CompoundButtonAppWidget.kt b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/CompoundButtonAppWidget.kt
index 6c4eaec..fe0d90e 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/CompoundButtonAppWidget.kt
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/CompoundButtonAppWidget.kt
@@ -80,6 +80,7 @@
             var checkbox3Checked by remember { mutableStateOf(false) }
             var switch1Checked by remember { mutableStateOf(false) }
             var switch2Checked by remember { mutableStateOf(false) }
+            @Suppress("AutoboxingStateCreation")
             var radioChecked by remember { mutableStateOf(0) }
 
             CheckBox(
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/RippleAppWidget.kt b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/RippleAppWidget.kt
index d25e148..8fea071 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/RippleAppWidget.kt
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/RippleAppWidget.kt
@@ -71,6 +71,7 @@
 
     @Composable
     private fun RippleDemoContent() {
+        @Suppress("AutoboxingStateCreation")
         var count by remember { mutableStateOf(0) }
         var type by remember { mutableStateOf(ContentScale.Fit) }
         var columnBgColors by remember { mutableStateOf(columnBgColorsA) }
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/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 20fca0b..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
@@ -182,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)
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 c3e9355..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
@@ -141,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())
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 09228b3..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
@@ -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/NormalizeCompositionTree.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/NormalizeCompositionTree.kt
index f48c4f7..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
@@ -71,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
@@ -98,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
         }
     ) {
@@ -121,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)
@@ -142,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>>()
@@ -363,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/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-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/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 0763485..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
@@ -104,7 +104,6 @@
  * <p>Provide [errorMessage] to explain which operation you were about to perform. This makes it
  * easier for developer to find where the failure happened.
  */
-@Suppress("ListIterator")
 internal fun <R> buildGeneralErrorMessage(
     errorMessage: String,
     nodes: List<GlanceNode<R>>
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
index 30d1ebf..cbe4572 100644
--- a/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeAssertionCollection.kt
+++ b/glance/glance-testing/src/main/java/androidx/glance/testing/GlanceNodeAssertionCollection.kt
@@ -72,7 +72,6 @@
         val errorMessageOnFail = "Failed to assertAll(${matcher.description})"
 
         val filteredNodes = testContext.findMatchingNodes(selector, errorMessageOnFail)
-        @Suppress("ListIterator")
         val violations = filteredNodes.filter {
             !matcher.matches(it)
         }
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 2357c19..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
@@ -45,7 +45,6 @@
             }
 
             collectAllNodesRecursive(rootGlanceNode)
-            @Suppress("ListIterator")
             this.allNodes = allNodes.toList()
         }
 
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 9404bae..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
@@ -52,7 +52,6 @@
  * An implementation of [GlanceNode] node that uses [MappedNode] to perform assertions during
  * testing.
  */
-@Suppress("ListIterator")
 class GlanceMappedNode(private val mappedNode: MappedNode) : GlanceNode<MappedNode>(mappedNode) {
 
     @RestrictTo(Scope.LIBRARY_GROUP)
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 f6372ae..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
@@ -132,7 +132,6 @@
     substring: Boolean = false,
     ignoreCase: Boolean = false
 ): Boolean {
-    @Suppress("ListIterator")
     val contentDescription =
         semanticsModifier.configuration.getOrNull(SemanticsProperties.ContentDescription)
             ?.joinToString()
@@ -285,7 +284,6 @@
             return true
         }
 
-        @Suppress("ListIterator")
         return node.children().any { checkIfSubtreeMatchesRecursive(matcher, it) }
     }
 
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 10b5fb0..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
@@ -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
@@ -150,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()
     }
 
@@ -158,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()
     }
 
@@ -333,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))
             }
         }
@@ -353,7 +351,7 @@
             .setHeight(height.toContainerDimension())
             .setVerticalAlignment(element.verticalAlignment.toProto())
             .also { row ->
-                element.children.fastForEach {
+                element.children.forEach {
                     row.addContent(translateComposition(context, resourceBuilder, it))
                 }
             }
@@ -393,7 +391,7 @@
             .setWidth(width.toContainerDimension())
             .setHorizontalAlignment(element.horizontalAlignment.toProto())
             .also { column ->
-                element.children.fastForEach {
+                element.children.forEach {
                     column.addContent(translateComposition(context, resourceBuilder, it))
                 }
             }
@@ -639,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/build.gradle b/glance/glance/build.gradle
index d6cade7..0a8e136 100644
--- a/glance/glance/build.gradle
+++ b/glance/glance/build.gradle
@@ -29,7 +29,6 @@
     api("androidx.compose.runtime:runtime:1.2.1")
     api("androidx.compose.ui:ui-graphics:1.1.1")
     api("androidx.compose.ui:ui-unit:1.1.1")
-    api("androidx.compose.ui:ui-util:1.6.0-alpha05")
     api("androidx.datastore:datastore-core:1.0.0")
     api("androidx.datastore:datastore-preferences-core:1.0.0")
     api("androidx.datastore:datastore-preferences:1.0.0")
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/Emittables.kt b/glance/glance/src/main/java/androidx/glance/Emittables.kt
index 042df8f..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,7 @@
     val children: MutableList<Emittable> = mutableListOf<Emittable>()
 
     protected fun childrenToString(): String =
-        children.fastJoinToString(",\n").prependIndent("  ")
+        children.joinToString(",\n").prependIndent("  ")
 }
 
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
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/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/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/gradle.properties b/gradle.properties
index 1e316c3..a48d1e5 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -24,7 +24,7 @@
 android.lint.baselineOmitLineNumbers=true
 android.lint.printStackTrace=true
 android.builder.sdkDownload=false
-android.uniquePackageNames=false
+android.uniquePackageNames=true
 android.enableAdditionalTestOutput=true
 android.useAndroidX=true
 android.nonTransitiveRClass=true
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 88838d0..b756b0c 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -2,13 +2,13 @@
 # -----------------------------------------------------------------------------
 # All of the following should be updated in sync.
 # -----------------------------------------------------------------------------
-androidGradlePlugin = "8.2.0-beta01"
+androidGradlePlugin = "8.3.0-alpha02"
 # NOTE: When updating the lint version we also need to update the `api` version
 # supported by `IssueRegistry`'s.' For e.g. r.android.com/1331903
-androidLint = "31.2.0-beta01"
+androidLint = "31.3.0-alpha02"
 # Once you have a chosen version of AGP to upgrade to, go to
 # https://developer.android.com/studio/archive and find the matching version of Studio.
-androidStudio = "2023.1.1.17"
+androidStudio = "2023.2.1.2"
 # -----------------------------------------------------------------------------
 
 androidGradlePluginMin = "7.0.4"
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index a869424..d3b3695 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -577,6 +577,14 @@
             <sha256 value="663aed69db1623331032fe4dedee4f2b1c9decbdad0ab06a4fc0be14b3e52c7f" origin="Generated by Gradle" reason="Artifact is not signed"/>
          </artifact>
       </component>
+      <component group="com.google.android.libraries.identity.googleid" name="googleid" version="1.1.0">
+         <artifact name="googleid-1.1.0.aar">
+             <sha256 value="194ac1fc1986dd1f62046fae37ddf77e63770fdc1f3d34baaa397cfbf4d191a2" origin="Generated by Gradle" reason="Artifact is not signed. b/300156232"/>
+         </artifact>
+         <artifact name="googleid-1.1.0.pom">
+            <sha256 value="a8475b43e61aedc85ad9f66c47278dd4aed0c8ff426961035b72c8b8395b1921" origin="Generated by Gradle" reason="Artifact is not signed"/>
+         </artifact>
+      </component>
       <component group="com.google.android.odml" name="image" version="1.0.0-beta1">
          <artifact name="image-1.0.0-beta1.aar">
             <sha256 value="2e71aa31f83a9415277f119de67195726f07d1760e9542c111778c320e3aa1f2" origin="Generated by Gradle" reason="Artifact is not signed"/>
diff --git a/health/connect/connect-client/lint-baseline.xml b/health/connect/connect-client/lint-baseline.xml
index 1959b87c..3434d5f 100644
--- a/health/connect/connect-client/lint-baseline.xml
+++ b/health/connect/connect-client/lint-baseline.xml
@@ -1663,7 +1663,7 @@
         errorLine1="                        PermissionProto.Permission.newBuilder().setPermission(it).build()"
         errorLine2="                                                                                  ~~~~~">
         <location
-            file="src/main/java/androidx/health/connect/client/permission/HealthDataRequestPermissionsInternal.kt"/>
+            file="src/main/java/androidx/health/connect/client/permission/HealthPermissionsRequestAppContract.kt"/>
     </issue>
 
     <issue
@@ -1672,7 +1672,7 @@
         errorLine1="                        PermissionProto.Permission.newBuilder().setPermission(it).build()"
         errorLine2="                                                                ~~~~~~~~~~~~~">
         <location
-            file="src/main/java/androidx/health/connect/client/permission/HealthDataRequestPermissionsInternal.kt"/>
+            file="src/main/java/androidx/health/connect/client/permission/HealthPermissionsRequestAppContract.kt"/>
     </issue>
 
     <issue
@@ -1681,7 +1681,7 @@
         errorLine1="                        PermissionProto.Permission.newBuilder().setPermission(it).build()"
         errorLine2="                                                   ~~~~~~~~~~">
         <location
-            file="src/main/java/androidx/health/connect/client/permission/HealthDataRequestPermissionsInternal.kt"/>
+            file="src/main/java/androidx/health/connect/client/permission/HealthPermissionsRequestAppContract.kt"/>
     </issue>
 
     <issue
@@ -1690,7 +1690,7 @@
         errorLine1="                ?.map { it.proto.permission }"
         errorLine2="                                 ~~~~~~~~~~">
         <location
-            file="src/main/java/androidx/health/connect/client/permission/HealthDataRequestPermissionsInternal.kt"/>
+            file="src/main/java/androidx/health/connect/client/permission/HealthPermissionsRequestAppContract.kt"/>
     </issue>
 
     <issue
@@ -1699,7 +1699,7 @@
         errorLine1="                ?.map { it.proto.permission }"
         errorLine2="                                 ~~~~~~~~~~">
         <location
-            file="src/main/java/androidx/health/connect/client/permission/HealthDataRequestPermissionsInternal.kt"/>
+            file="src/main/java/androidx/health/connect/client/permission/HealthPermissionsRequestAppContract.kt"/>
     </issue>
 
     <issue
diff --git a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/RequestExerciseRouteUpsideDownCakeTest.kt b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/ExerciseRouteRequestModuleContractTest.kt
similarity index 89%
rename from health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/RequestExerciseRouteUpsideDownCakeTest.kt
rename to health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/ExerciseRouteRequestModuleContractTest.kt
index ea4548d..c19f393 100644
--- a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/RequestExerciseRouteUpsideDownCakeTest.kt
+++ b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/ExerciseRouteRequestModuleContractTest.kt
@@ -24,7 +24,7 @@
 import androidx.health.connect.client.impl.platform.records.PlatformExerciseRoute
 import androidx.health.connect.client.impl.platform.records.PlatformExerciseRouteLocationBuilder
 import androidx.health.connect.client.impl.platform.records.PlatformLength
-import androidx.health.connect.client.permission.platform.RequestExerciseRouteUpsideDownCake
+import androidx.health.connect.client.permission.platform.ExerciseRouteRequestModuleContract
 import androidx.health.connect.client.records.ExerciseRoute
 import androidx.health.connect.client.units.Length
 import androidx.test.core.app.ApplicationProvider
@@ -42,7 +42,7 @@
 @TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
 // Comment the SDK suppress to run on emulators lower than U.
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
-class RequestExerciseRouteUpsideDownCakeTest {
+class ExerciseRouteRequestModuleContractTest {
 
     private lateinit var context: Context
 
@@ -53,7 +53,7 @@
 
     @Test
     fun createIntentTest() {
-        val requestRouteContract = RequestExerciseRouteUpsideDownCake()
+        val requestRouteContract = ExerciseRouteRequestModuleContract()
         val intent = requestRouteContract.createIntent(context, "someUid")
         assertThat(intent.action).isEqualTo("android.health.connect.action.REQUEST_EXERCISE_ROUTE")
         assertThat(intent.getStringExtra("android.health.connect.extra.SESSION_ID"))
@@ -62,21 +62,21 @@
 
     @Test
     fun parseIntent_null() {
-        val requestRouteContract = RequestExerciseRouteUpsideDownCake()
+        val requestRouteContract = ExerciseRouteRequestModuleContract()
         val result = requestRouteContract.parseResult(0, null)
         assertThat(result).isNull()
     }
 
     @Test
     fun parseIntent_emptyIntent() {
-        val requestRouteContract = RequestExerciseRouteUpsideDownCake()
+        val requestRouteContract = ExerciseRouteRequestModuleContract()
         val result = requestRouteContract.parseResult(0, Intent())
         assertThat(result).isNull()
     }
 
     @Test
     fun parseIntent_emptyRoute() {
-        val requestRouteContract = RequestExerciseRouteUpsideDownCake()
+        val requestRouteContract = ExerciseRouteRequestModuleContract()
         val intent = Intent()
         intent.putExtra(HealthConnectManager.EXTRA_EXERCISE_ROUTE, PlatformExerciseRoute(listOf()))
         val result = requestRouteContract.parseResult(0, intent)
@@ -85,7 +85,7 @@
 
     @Test
     fun parseIntent() {
-        val requestRouteContract = RequestExerciseRouteUpsideDownCake()
+        val requestRouteContract = ExerciseRouteRequestModuleContract()
         val intent = Intent()
         val location1 =
             PlatformExerciseRouteLocationBuilder(Instant.ofEpochMilli(1234L), 23.4, -23.4)
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/PermissionController.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/PermissionController.kt
index 1a840d2..bf1963e 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/PermissionController.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/PermissionController.kt
@@ -19,8 +19,8 @@
 import androidx.annotation.RestrictTo
 import androidx.health.connect.client.HealthConnectClient.Companion.DEFAULT_PROVIDER_PACKAGE_NAME
 import androidx.health.connect.client.contracts.HealthPermissionsRequestContract
-import androidx.health.connect.client.permission.HealthDataRequestPermissionsInternal
 import androidx.health.connect.client.permission.HealthPermission
+import androidx.health.connect.client.permission.HealthPermissionsRequestAppContract
 
 @JvmDefaultWithCompatibility
 /** Interface for operations related to permissions. */
@@ -54,7 +54,7 @@
         fun createRequestPermissionResultContractLegacy(
             providerPackageName: String = DEFAULT_PROVIDER_PACKAGE_NAME
         ): ActivityResultContract<Set<String>, Set<String>> {
-            return HealthDataRequestPermissionsInternal(providerPackageName = providerPackageName)
+            return HealthPermissionsRequestAppContract(providerPackageName = providerPackageName)
         }
 
         /**
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/contracts/ExerciseRouteRequestContract.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/contracts/ExerciseRouteRequestContract.kt
index e936736..84a81bc 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/contracts/ExerciseRouteRequestContract.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/contracts/ExerciseRouteRequestContract.kt
@@ -20,8 +20,8 @@
 import android.content.Intent
 import android.os.Build
 import androidx.activity.result.contract.ActivityResultContract
-import androidx.health.connect.client.permission.RequestExerciseRouteInternal
-import androidx.health.connect.client.permission.platform.RequestExerciseRouteUpsideDownCake
+import androidx.health.connect.client.permission.ExerciseRouteRequestAppContract
+import androidx.health.connect.client.permission.platform.ExerciseRouteRequestModuleContract
 import androidx.health.connect.client.records.ExerciseRoute
 
 /**
@@ -36,9 +36,9 @@
 
     private val delegate: ActivityResultContract<String, ExerciseRoute?> =
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
-            RequestExerciseRouteUpsideDownCake()
+            ExerciseRouteRequestModuleContract()
         } else {
-            RequestExerciseRouteInternal()
+            ExerciseRouteRequestAppContract()
         }
 
     /**
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/contracts/HealthPermissionsRequestContract.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/contracts/HealthPermissionsRequestContract.kt
index 679412f..1b49a42 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/contracts/HealthPermissionsRequestContract.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/contracts/HealthPermissionsRequestContract.kt
@@ -21,9 +21,9 @@
 import android.os.Build
 import androidx.activity.result.contract.ActivityResultContract
 import androidx.health.connect.client.HealthConnectClient
-import androidx.health.connect.client.permission.HealthDataRequestPermissionsInternal
 import androidx.health.connect.client.permission.HealthPermission
-import androidx.health.connect.client.permission.platform.HealthDataRequestPermissionsUpsideDownCake
+import androidx.health.connect.client.permission.HealthPermissionsRequestAppContract
+import androidx.health.connect.client.permission.platform.HealthPermissionsRequestModuleContract
 
 /**
  * An [ActivityResultContract] to request Health permissions.
@@ -37,9 +37,9 @@
 
     private val delegate: ActivityResultContract<Set<String>, Set<String>> =
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
-            HealthDataRequestPermissionsUpsideDownCake()
+            HealthPermissionsRequestModuleContract()
         } else {
-            HealthDataRequestPermissionsInternal(providerPackageName)
+            HealthPermissionsRequestAppContract(providerPackageName)
         }
 
     /**
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/RequestExerciseRouteInternal.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/ExerciseRouteRequestAppContract.kt
similarity index 92%
rename from health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/RequestExerciseRouteInternal.kt
rename to health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/ExerciseRouteRequestAppContract.kt
index a21c0a3..2a1c981 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/RequestExerciseRouteInternal.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/ExerciseRouteRequestAppContract.kt
@@ -27,12 +27,13 @@
 import androidx.health.platform.client.service.HealthDataServiceConstants
 
 /**
- * An [ActivityResultContract] to request a route associated with an {@code ExerciseSessionRecord}.
+ * An [ActivityResultContract] to request a route associated with an {@code ExerciseSessionRecord}
+ * from the HealthConnect APK.
  *
  * @see androidx.activity.ComponentActivity.registerForActivityResult
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
-internal class RequestExerciseRouteInternal : ActivityResultContract<String, ExerciseRoute?>() {
+internal class ExerciseRouteRequestAppContract : ActivityResultContract<String, ExerciseRoute?>() {
     override fun createIntent(context: Context, input: String): Intent {
         return Intent(HealthDataServiceConstants.ACTION_REQUEST_ROUTE).apply {
             putExtra(HealthDataServiceConstants.EXTRA_SESSION_ID, input)
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/HealthDataRequestPermissionsInternal.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/HealthPermissionsRequestAppContract.kt
similarity index 97%
rename from health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/HealthDataRequestPermissionsInternal.kt
rename to health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/HealthPermissionsRequestAppContract.kt
index 6ddad08..004c375 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/HealthDataRequestPermissionsInternal.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/HealthPermissionsRequestAppContract.kt
@@ -29,14 +29,14 @@
 import androidx.health.platform.client.service.HealthDataServiceConstants.KEY_REQUESTED_PERMISSIONS_STRING
 
 /**
- * An [ActivityResultContract] to request Health Connect permissions.
+ * An [ActivityResultContract] to request Health Connect permissions from the HealthConnect APK.
  *
  * @param providerPackageName Optional provider package name for the backing implementation of
  *   choice.
  * @see androidx.activity.ComponentActivity.registerForActivityResult
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
-internal class HealthDataRequestPermissionsInternal(
+internal class HealthPermissionsRequestAppContract(
     private val providerPackageName: String = DEFAULT_PROVIDER_PACKAGE_NAME,
 ) : ActivityResultContract<Set<String>, Set<String>>() {
 
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/platform/RequestExerciseRouteUpsideDownCake.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/platform/ExerciseRouteRequestModuleContract.kt
similarity index 94%
rename from health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/platform/RequestExerciseRouteUpsideDownCake.kt
rename to health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/platform/ExerciseRouteRequestModuleContract.kt
index b0c7a42..363ba7c 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/platform/RequestExerciseRouteUpsideDownCake.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/platform/ExerciseRouteRequestModuleContract.kt
@@ -29,13 +29,14 @@
 import androidx.health.platform.client.impl.logger.Logger
 
 /**
- * An [ActivityResultContract] to request a route associated with an {@code ExerciseSessionRecord}.
+ * An [ActivityResultContract] to request a route associated with an {@code ExerciseSessionRecord}
+ * from HealthConnect in the Android Platform.
  *
  * @see androidx.activity.ComponentActivity.registerForActivityResult
  */
 @RequiresApi(34)
 @RestrictTo(RestrictTo.Scope.LIBRARY)
-internal class RequestExerciseRouteUpsideDownCake :
+internal class ExerciseRouteRequestModuleContract :
     ActivityResultContract<String, ExerciseRoute?>() {
     override fun createIntent(context: Context, input: String): Intent {
         return Intent(HealthConnectManager.ACTION_REQUEST_EXERCISE_ROUTE).apply {
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/platform/HealthDataRequestPermissionsUpsideDownCake.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/platform/HealthPermissionsRequestModuleContract.kt
similarity index 96%
rename from health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/platform/HealthDataRequestPermissionsUpsideDownCake.kt
rename to health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/platform/HealthPermissionsRequestModuleContract.kt
index e4dd720..41091f9 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/platform/HealthDataRequestPermissionsUpsideDownCake.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/platform/HealthPermissionsRequestModuleContract.kt
@@ -27,7 +27,7 @@
  * @see androidx.activity.ComponentActivity.registerForActivityResult
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
-internal class HealthDataRequestPermissionsUpsideDownCake :
+internal class HealthPermissionsRequestModuleContract :
     ActivityResultContract<Set<String>, Set<String>>() {
 
     private val requestPermissions = RequestMultiplePermissions()
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/RequestExerciseRouteInternalTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/ExerciseRouteRequestAppContractTest.kt
similarity index 92%
rename from health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/RequestExerciseRouteInternalTest.kt
rename to health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/ExerciseRouteRequestAppContractTest.kt
index b650485..b3cfbc9 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/RequestExerciseRouteInternalTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/ExerciseRouteRequestAppContractTest.kt
@@ -30,7 +30,7 @@
 import org.junit.runner.RunWith
 
 @RunWith(AndroidJUnit4::class)
-class RequestExerciseRouteInternalTest {
+class ExerciseRouteRequestAppContractTest {
 
     private lateinit var context: Context
 
@@ -41,7 +41,7 @@
 
     @Test
     fun createIntentTest() {
-        val requestRouteContract = RequestExerciseRouteInternal()
+        val requestRouteContract = ExerciseRouteRequestAppContract()
         val intent = requestRouteContract.createIntent(context, "someUid")
         assertThat(intent.action).isEqualTo("androidx.health.action.REQUEST_EXERCISE_ROUTE")
         assertThat(intent.getStringExtra(HealthDataServiceConstants.EXTRA_SESSION_ID))
@@ -50,21 +50,21 @@
 
     @Test
     fun parseIntent_null() {
-        val requestRouteContract = RequestExerciseRouteInternal()
+        val requestRouteContract = ExerciseRouteRequestAppContract()
         val result = requestRouteContract.parseResult(0, null)
         assertThat(result).isNull()
     }
 
     @Test
     fun parseIntent_emptyIntent() {
-        val requestRouteContract = RequestExerciseRouteInternal()
+        val requestRouteContract = ExerciseRouteRequestAppContract()
         val result = requestRouteContract.parseResult(0, Intent())
         assertThat(result).isNull()
     }
 
     @Test
     fun parseIntent_emptyRoute() {
-        val requestRouteContract = RequestExerciseRouteInternal()
+        val requestRouteContract = ExerciseRouteRequestAppContract()
         val intent = Intent()
         intent.putExtra(
             HealthDataServiceConstants.EXTRA_EXERCISE_ROUTE,
@@ -78,7 +78,7 @@
 
     @Test
     fun parseIntent() {
-        val requestRouteContract = RequestExerciseRouteInternal()
+        val requestRouteContract = ExerciseRouteRequestAppContract()
         val intent = Intent()
         val protoLocation1 =
             DataProto.SubTypeDataValue.newBuilder()
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/HealthDataRequestPermissionsInternalTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/HealthPermissionsRequestAppContractTest.kt
similarity index 88%
rename from health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/HealthDataRequestPermissionsInternalTest.kt
rename to health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/HealthPermissionsRequestAppContractTest.kt
index df6fd02..961e65b 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/HealthDataRequestPermissionsInternalTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/HealthPermissionsRequestAppContractTest.kt
@@ -32,7 +32,7 @@
 private const val TEST_PACKAGE = "com.test.app"
 
 @RunWith(AndroidJUnit4::class)
-class HealthDataRequestPermissionsInternalTest {
+class HealthPermissionsRequestAppContractTest {
 
     private lateinit var context: Context
 
@@ -43,7 +43,7 @@
 
     @Test
     fun createIntentTest() {
-        val requestPermissionContract = HealthDataRequestPermissionsInternal(TEST_PACKAGE)
+        val requestPermissionContract = HealthPermissionsRequestAppContract(TEST_PACKAGE)
         val intent =
             requestPermissionContract.createIntent(
                 context,
@@ -78,7 +78,7 @@
 
     @Test
     fun createIntent_defaultPackage() {
-        val requestPermissionContract = HealthDataRequestPermissionsInternal()
+        val requestPermissionContract = HealthPermissionsRequestAppContract()
         val intent =
             requestPermissionContract.createIntent(context, setOf(HealthPermission.READ_STEPS))
 
@@ -89,7 +89,7 @@
 
     @Test
     fun parseIntent_null_fallback() {
-        val requestPermissionContract = HealthDataRequestPermissionsInternal(TEST_PACKAGE)
+        val requestPermissionContract = HealthPermissionsRequestAppContract(TEST_PACKAGE)
         val result = requestPermissionContract.parseResult(0, null)
 
         Truth.assertThat(result).isEmpty()
@@ -97,7 +97,7 @@
 
     @Test
     fun parseIntent_emptyIntent() {
-        val requestPermissionContract = HealthDataRequestPermissionsInternal(TEST_PACKAGE)
+        val requestPermissionContract = HealthPermissionsRequestAppContract(TEST_PACKAGE)
         val result = requestPermissionContract.parseResult(0, Intent())
 
         Truth.assertThat(result).isEmpty()
@@ -105,7 +105,7 @@
 
     @Test
     fun parseIntent() {
-        val requestPermissionContract = HealthDataRequestPermissionsInternal(TEST_PACKAGE)
+        val requestPermissionContract = HealthPermissionsRequestAppContract(TEST_PACKAGE)
         val intent = Intent()
         intent.putParcelableArrayListExtra(
             HealthDataServiceConstants.KEY_GRANTED_PERMISSIONS_STRING,
@@ -130,7 +130,7 @@
 
     @Test
     fun synchronousResult_null() {
-        val requestPermissionContract = HealthDataRequestPermissionsInternal(TEST_PACKAGE)
+        val requestPermissionContract = HealthPermissionsRequestAppContract(TEST_PACKAGE)
         val result =
             requestPermissionContract.getSynchronousResult(
                 context,
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/platform/HealthDataRequestPermissionsUpsideDownCakeTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/platform/HealthPermissionsRequestModuleContractTest.kt
similarity index 92%
rename from health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/platform/HealthDataRequestPermissionsUpsideDownCakeTest.kt
rename to health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/platform/HealthPermissionsRequestModuleContractTest.kt
index f284bd6a..b6d7cb2 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/platform/HealthDataRequestPermissionsUpsideDownCakeTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/platform/HealthPermissionsRequestModuleContractTest.kt
@@ -30,7 +30,7 @@
 import org.junit.runner.RunWith
 
 @RunWith(AndroidJUnit4::class)
-class HealthDataRequestPermissionsUpsideDownCakeTest {
+class HealthPermissionsRequestModuleContractTest {
 
     private lateinit var context: Context
 
@@ -41,7 +41,7 @@
 
     @Test
     fun createIntent() {
-        val requestPermissionContract = HealthDataRequestPermissionsUpsideDownCake()
+        val requestPermissionContract = HealthPermissionsRequestModuleContract()
         val intent =
             requestPermissionContract.createIntent(
                 context,
@@ -56,7 +56,7 @@
 
     @Test
     fun parseIntent() {
-        val requestPermissionContract = HealthDataRequestPermissionsUpsideDownCake()
+        val requestPermissionContract = HealthPermissionsRequestModuleContract()
 
         val intent = Intent()
         intent.putExtra(
diff --git a/hilt/integration-tests/viewmodelapp/lint-baseline.xml b/hilt/integration-tests/viewmodelapp/lint-baseline.xml
new file mode 100644
index 0000000..18c2ee5
--- /dev/null
+++ b/hilt/integration-tests/viewmodelapp/lint-baseline.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.3.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
+
+    <issue
+        id="MissingClass"
+        message="Class referenced in the manifest, `androidx.hilt.integration.viewmodelapp.ActivityInjectionTest$TestActivity`, was not found in the project or the libraries"
+        errorLine1="        &lt;activity android:name=&quot;.ActivityInjectionTest$TestActivity&quot;/>"
+        errorLine2="                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/debug/AndroidManifest.xml"/>
+    </issue>
+
+    <issue
+        id="MissingClass"
+        message="Class referenced in the manifest, `androidx.hilt.integration.viewmodelapp.BaseActivityInjectionTest$TestActivity`, was not found in the project or the libraries"
+        errorLine1="        &lt;activity android:name=&quot;.BaseActivityInjectionTest$TestActivity&quot;/>"
+        errorLine2="                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/debug/AndroidManifest.xml"/>
+    </issue>
+
+    <issue
+        id="MissingClass"
+        message="Class referenced in the manifest, `androidx.hilt.integration.viewmodelapp.FragmentInjectionTest$TestActivity`, was not found in the project or the libraries"
+        errorLine1="        &lt;activity android:name=&quot;.FragmentInjectionTest$TestActivity&quot;/>"
+        errorLine2="                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/debug/AndroidManifest.xml"/>
+    </issue>
+
+    <issue
+        id="MissingClass"
+        message="Class referenced in the manifest, `androidx.hilt.integration.viewmodelapp.BaseFragmentInjectionTest$TestActivity`, was not found in the project or the libraries"
+        errorLine1="        &lt;activity android:name=&quot;.BaseFragmentInjectionTest$TestActivity&quot;/>"
+        errorLine2="                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="src/debug/AndroidManifest.xml"/>
+    </issue>
+
+</issues>
diff --git a/leanback/leanback/lint-baseline.xml b/leanback/leanback/lint-baseline.xml
index 6b97ced..d5e9e66 100644
--- a/leanback/leanback/lint-baseline.xml
+++ b/leanback/leanback/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-beta05" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta05)" variant="all" version="8.1.0-beta05">
+<issues format="6" by="lint 8.3.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
 
     <issue
         id="NewApi"
@@ -2494,42 +2494,6 @@
     <issue
         id="UnknownNullness"
         message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public View onCreateView(LayoutInflater inflater, ViewGroup container,"
-        errorLine2="           ~~~~">
-        <location
-            file="src/main/java/androidx/leanback/app/BrowseSupportFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public View onCreateView(LayoutInflater inflater, ViewGroup container,"
-        errorLine2="                             ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/leanback/app/BrowseSupportFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public View onCreateView(LayoutInflater inflater, ViewGroup container,"
-        errorLine2="                                                      ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/leanback/app/BrowseSupportFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="            Bundle savedInstanceState) {"
-        errorLine2="            ~~~~~~">
-        <location
-            file="src/main/java/androidx/leanback/app/BrowseSupportFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
         errorLine1="    public void setHeaderPresenterSelector(PresenterSelector headerPresenterSelector) {"
         errorLine2="                                           ~~~~~~~~~~~~~~~~~">
         <location
@@ -3070,42 +3034,6 @@
     <issue
         id="UnknownNullness"
         message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public View onCreateView(LayoutInflater inflater, ViewGroup container,"
-        errorLine2="           ~~~~">
-        <location
-            file="src/main/java/androidx/leanback/app/DetailsSupportFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public View onCreateView(LayoutInflater inflater, ViewGroup container,"
-        errorLine2="                             ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/leanback/app/DetailsSupportFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public View onCreateView(LayoutInflater inflater, ViewGroup container,"
-        errorLine2="                                                      ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/leanback/app/DetailsSupportFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="            Bundle savedInstanceState) {"
-        errorLine2="            ~~~~~~">
-        <location
-            file="src/main/java/androidx/leanback/app/DetailsSupportFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
         errorLine1="    protected void setupPresenter(Presenter rowPresenter) {"
         errorLine2="                                  ~~~~~~~~~">
         <location
@@ -6472,42 +6400,6 @@
     <issue
         id="UnknownNullness"
         message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public View onCreateView(LayoutInflater inflater, ViewGroup container,"
-        errorLine2="           ~~~~">
-        <location
-            file="src/main/java/androidx/leanback/app/PlaybackSupportFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public View onCreateView(LayoutInflater inflater, ViewGroup container,"
-        errorLine2="                             ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/leanback/app/PlaybackSupportFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public View onCreateView(LayoutInflater inflater, ViewGroup container,"
-        errorLine2="                                                      ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/leanback/app/PlaybackSupportFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="                             Bundle savedInstanceState) {"
-        errorLine2="                             ~~~~~~">
-        <location
-            file="src/main/java/androidx/leanback/app/PlaybackSupportFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
         errorLine1="    public void setHostCallback(PlaybackGlueHost.HostCallback hostCallback) {"
         errorLine2="                                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
@@ -8029,42 +7921,6 @@
     <issue
         id="UnknownNullness"
         message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public View onCreateView(LayoutInflater inflater, ViewGroup container,"
-        errorLine2="           ~~~~">
-        <location
-            file="src/main/java/androidx/leanback/app/SearchSupportFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public View onCreateView(LayoutInflater inflater, ViewGroup container,"
-        errorLine2="                             ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/leanback/app/SearchSupportFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public View onCreateView(LayoutInflater inflater, ViewGroup container,"
-        errorLine2="                                                      ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/leanback/app/SearchSupportFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="                             Bundle savedInstanceState) {"
-        errorLine2="                             ~~~~~~">
-        <location
-            file="src/main/java/androidx/leanback/app/SearchSupportFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
         errorLine1="    public RowsSupportFragment getRowsSupportFragment() {"
         errorLine2="           ~~~~~~~~~~~~~~~~~~~">
         <location
@@ -9010,42 +8866,6 @@
     <issue
         id="UnknownNullness"
         message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public View onCreateView("
-        errorLine2="           ~~~~">
-        <location
-            file="src/main/java/androidx/leanback/app/VideoSupportFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {"
-        errorLine2="            ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/leanback/app/VideoSupportFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {"
-        errorLine2="                                     ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/leanback/app/VideoSupportFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {"
-        errorLine2="                                                          ~~~~~~">
-        <location
-            file="src/main/java/androidx/leanback/app/VideoSupportFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
         errorLine1="    public void setSurfaceHolderCallback(SurfaceHolder.Callback callback) {"
         errorLine2="                                         ~~~~~~~~~~~~~~~~~~~~~~">
         <location
diff --git a/libraryversions.toml b/libraryversions.toml
index 1f1cb09..1f53b93 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -1,5 +1,5 @@
 [versions]
-ACTIVITY = "1.8.0-beta01"
+ACTIVITY = "1.8.0-rc01"
 ANNOTATION = "1.8.0-alpha01"
 ANNOTATION_EXPERIMENTAL = "1.4.0-alpha01"
 APPACTIONS_BUILTINTYPES = "1.0.0-alpha01"
@@ -9,7 +9,7 @@
 ARCH_CORE = "2.3.0-alpha01"
 ASYNCLAYOUTINFLATER = "1.1.0-alpha02"
 AUTOFILL = "1.3.0-alpha02"
-BENCHMARK = "1.2.0-beta05"
+BENCHMARK = "1.3.0-alpha01"
 BIOMETRIC = "1.2.0-alpha06"
 BLUETOOTH = "1.0.0-alpha01"
 BROWSER = "1.7.0-alpha01"
@@ -19,7 +19,7 @@
 CARDVIEW = "1.1.0-alpha01"
 CAR_APP = "1.4.0-beta02"
 COLLECTION = "1.4.0-alpha01"
-COMPOSE = "1.6.0-alpha05"
+COMPOSE = "1.6.0-alpha06"
 COMPOSE_COMPILER = "1.5.3"
 COMPOSE_MATERIAL3 = "1.2.0-alpha08"
 COMPOSE_MATERIAL3_ADAPTIVE = "1.0.0-alpha01"
@@ -58,7 +58,7 @@
 EMOJI2 = "1.5.0-alpha01"
 ENTERPRISE = "1.1.0-rc01"
 EXIFINTERFACE = "1.4.0-alpha01"
-FRAGMENT = "1.7.0-alpha04"
+FRAGMENT = "1.7.0-alpha05"
 FUTURES = "1.2.0-alpha02"
 GLANCE = "1.1.0-alpha01"
 GLANCE_PREVIEW = "1.0.0-alpha06"
@@ -69,7 +69,7 @@
 GRAPHICS_PATH = "1.0.0-alpha02"
 GRAPHICS_SHAPES = "1.0.0-alpha03"
 GRIDLAYOUT = "1.1.0-beta02"
-HEALTH_CONNECT = "1.1.0-alpha04"
+HEALTH_CONNECT = "1.1.0-alpha05"
 HEALTH_SERVICES_CLIENT = "1.1.0-alpha02"
 HEIFWRITER = "1.1.0-alpha03"
 HILT = "1.1.0-alpha02"
@@ -146,8 +146,8 @@
 VIEWPAGER = "1.1.0-alpha02"
 VIEWPAGER2 = "1.1.0-beta03"
 WEAR = "1.4.0-alpha01"
-WEAR_COMPOSE = "1.3.0-alpha05"
-WEAR_COMPOSE_MATERIAL3 = "1.0.0-alpha11"
+WEAR_COMPOSE = "1.3.0-alpha06"
+WEAR_COMPOSE_MATERIAL3 = "1.0.0-alpha12"
 WEAR_INPUT = "1.2.0-alpha03"
 WEAR_INPUT_TESTING = "1.2.0-alpha03"
 WEAR_ONGOING = "1.1.0-alpha02"
diff --git a/media2/integration-tests/testapp/build.gradle b/media2/integration-tests/testapp/build.gradle
index 3c9fbfe..ef3049b 100644
--- a/media2/integration-tests/testapp/build.gradle
+++ b/media2/integration-tests/testapp/build.gradle
@@ -14,13 +14,6 @@
  * limitations under the License.
  */
 
-buildscript {
-    // TODO: Remove this when this test app no longer depends on 1.0.0 of vectordrawable-animated.
-    // vectordrawable and vectordrawable-animated were accidentally using the same package name
-    // which is no longer valid in namespaced resource world.
-    project.ext["android.uniquePackageNames"] = false
-}
-
 plugins {
     id("AndroidXPlugin")
     id("com.android.application")
diff --git a/navigation/integration-tests/testapp/build.gradle b/navigation/integration-tests/testapp/build.gradle
index 97a9bec..f235d73 100644
--- a/navigation/integration-tests/testapp/build.gradle
+++ b/navigation/integration-tests/testapp/build.gradle
@@ -14,13 +14,6 @@
  * limitations under the License.
  */
 
-buildscript {
-    // TODO: Remove this when this test app no longer depends on 1.0.0 of vectordrawable-animated.
-    // vectordrawable and vectordrawable-animated were accidentally using the same package name
-    // which is no longer valid in namespaced resource world.
-    project.ext["android.uniquePackageNames"] = false
-}
-
 plugins {
     id("AndroidXPlugin")
     id("com.android.application")
@@ -37,10 +30,14 @@
     implementation(project(":internal-testutils-navigation"), {
         exclude group: "androidx.navigation", module: "navigation-common"
     })
+    implementation(libs.multidex)
 }
 
 android {
     namespace "androidx.navigation.testapp"
+    defaultConfig {
+        multiDexEnabled true
+    }
 }
 
 tasks["check"].dependsOn(tasks["connectedCheck"])
diff --git a/navigation/navigation-fragment/lint-baseline.xml b/navigation/navigation-fragment/lint-baseline.xml
index f8e3e41..54ccdb0 100644
--- a/navigation/navigation-fragment/lint-baseline.xml
+++ b/navigation/navigation-fragment/lint-baseline.xml
@@ -1,11 +1,11 @@
 <?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.3.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
 
     <issue
         id="RestrictedApiAndroidX"
         message="FragmentManager.isLoggingEnabled can only be called from within the same library (androidx.fragment:fragment)"
-        errorLine1="                    if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {"
-        errorLine2="                                        ~~~~~~~~~~~~~~~~">
+        errorLine1="                if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {"
+        errorLine2="                                    ~~~~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/navigation/fragment/FragmentNavigator.kt"/>
     </issue>
@@ -22,8 +22,8 @@
     <issue
         id="RestrictedApiAndroidX"
         message="FragmentManager.isLoggingEnabled can only be called from within the same library (androidx.fragment:fragment)"
-        errorLine1="                    if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {"
-        errorLine2="                                        ~~~~~~~~~~~~~~~~">
+        errorLine1="                if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {"
+        errorLine2="                                    ~~~~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/navigation/fragment/FragmentNavigator.kt"/>
     </issue>
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 5ae8346..5846324 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
@@ -1618,10 +1618,23 @@
             emptyActivity.onBackPressed()
         }
 
+        val countDownLatch3 = CountDownLatch(1)
+        activityRule.runOnUiThread {
+            entry1.lifecycle.addObserver(object : LifecycleEventObserver {
+                override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
+                    if (event == Lifecycle.Event.ON_RESUME) {
+                        // wait for animator to finish
+                        countDownLatch3.countDown()
+                    }
+                }
+            })
+        }
+        assertThat(countDownLatch3.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+
         // assert exit from entry2 and enter entry1
         assertThat(fragmentNavigator.backStack.value).containsExactly(entry1)
-        assertThat(entry1.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
-        assertThat(entry2.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED)
+        assertThat(entry1.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
+        assertThat(entry2.lifecycle.currentState).isEqualTo(Lifecycle.State.DESTROYED)
     }
 
     @LargeTest
diff --git a/paging/paging-testing/build.gradle b/paging/paging-testing/build.gradle
index 4b85a2d..a17e9a1 100644
--- a/paging/paging-testing/build.gradle
+++ b/paging/paging-testing/build.gradle
@@ -15,14 +15,20 @@
  */
 
 import androidx.build.LibraryType
+import androidx.build.KmpPlatformsKt
 import androidx.build.PlatformIdentifier
+import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
 import androidx.build.Publish
+import org.jetbrains.kotlin.konan.target.Family
 
 plugins {
     id("AndroidXPlugin")
     id("com.android.library")
 }
 
+def macEnabled = KmpPlatformsKt.enableMac(project)
+def linuxEnabled = KmpPlatformsKt.enableLinux(project)
+
 androidXMultiplatform {
     jvm()
     mac()
@@ -33,20 +39,71 @@
     defaultPlatform(PlatformIdentifier.ANDROID)
 
     sourceSets {
-        androidMain {
+        commonMain {
             dependencies {
                 api(libs.kotlinStdlib)
                 implementation(project(":paging:paging-common"))
             }
         }
-        androidUnitTest {
+
+        androidMain {
+            dependsOn(jvmMain)
+        }
+
+        commonTest {
+            dependencies {
+                implementation(libs.kotlinTest)
+                implementation(libs.kotlinCoroutinesTest)
+                implementation(project(":internal-testutils-paging"))
+                implementation(project(":kruth:kruth"))
+            }
+        }
+
+        jvmTest {
+            dependsOn(commonTest)
             dependencies {
                 implementation(libs.junit)
-                implementation(libs.kotlinCoroutinesTest)
-                implementation((libs.kotlinCoroutinesAndroid))
-                implementation(project(":internal-testutils-paging"))
-                implementation(libs.kotlinTest)
-                implementation(libs.truth)
+            }
+        }
+
+        androidInstrumentedTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation(libs.testRunner)
+            }
+        }
+
+        if (macEnabled || linuxEnabled) {
+            nativeMain {
+                dependsOn(commonMain)
+                dependencies {
+                    implementation(libs.atomicFu)
+                }
+            }
+        }
+        if (macEnabled) {
+            darwinMain {
+                dependsOn(nativeMain)
+            }
+        }
+        if (linuxEnabled) {
+            linuxMain {
+                dependsOn(nativeMain)
+            }
+        }
+
+        targets.all { target ->
+            if (target.platformType == KotlinPlatformType.native) {
+                target.compilations["main"].defaultSourceSet {
+                    def konanTargetFamily = target.konanTarget.family
+                    if (konanTargetFamily == Family.OSX || konanTargetFamily == Family.IOS) {
+                        dependsOn(darwinMain)
+                    } else if (konanTargetFamily == Family.LINUX) {
+                        dependsOn(linuxMain)
+                    } else {
+                        throw new GradleException("unknown native target ${target}")
+                    }
+                }
             }
         }
     }
diff --git a/paging/paging-testing/src/androidMain/kotlin/androidx/paging/testing/LoadErrorHandler.kt b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/LoadErrorHandler.kt
similarity index 100%
rename from paging/paging-testing/src/androidMain/kotlin/androidx/paging/testing/LoadErrorHandler.kt
rename to paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/LoadErrorHandler.kt
diff --git a/paging/paging-testing/src/androidMain/kotlin/androidx/paging/testing/PagerFlowSnapshot.kt b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/PagerFlowSnapshot.kt
similarity index 99%
rename from paging/paging-testing/src/androidMain/kotlin/androidx/paging/testing/PagerFlowSnapshot.kt
rename to paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/PagerFlowSnapshot.kt
index 825b27b..c6dc443 100644
--- a/paging/paging-testing/src/androidMain/kotlin/androidx/paging/testing/PagerFlowSnapshot.kt
+++ b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/PagerFlowSnapshot.kt
@@ -33,6 +33,7 @@
 import androidx.paging.testing.LoaderCallback.CallbackType.ON_INSERTED
 import androidx.paging.testing.LoaderCallback.CallbackType.ON_REMOVED
 import kotlin.coroutines.CoroutineContext
+import kotlin.jvm.JvmSuppressWildcards
 import kotlinx.coroutines.cancelAndJoin
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
diff --git a/paging/paging-testing/src/androidMain/kotlin/androidx/paging/testing/SnapshotLoader.kt b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/SnapshotLoader.kt
similarity index 98%
rename from paging/paging-testing/src/androidMain/kotlin/androidx/paging/testing/SnapshotLoader.kt
rename to paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/SnapshotLoader.kt
index 1ebbdbf..b8258d3 100644
--- a/paging/paging-testing/src/androidMain/kotlin/androidx/paging/testing/SnapshotLoader.kt
+++ b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/SnapshotLoader.kt
@@ -25,8 +25,9 @@
 import androidx.paging.PagingDataDiffer
 import androidx.paging.PagingSource
 import androidx.paging.testing.LoaderCallback.CallbackType.ON_INSERTED
-import java.util.concurrent.atomic.AtomicInteger
-import java.util.concurrent.atomic.AtomicReference
+import androidx.paging.testing.internal.AtomicInt
+import androidx.paging.testing.internal.AtomicRef
+import kotlin.jvm.JvmSuppressWildcards
 import kotlin.math.abs
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.filterNotNull
@@ -451,12 +452,12 @@
      * Temporarily stores the latest [DifferCallback] to track prepends to the beginning of list.
      * Value is reset to null once read.
      */
-    val callbackState: AtomicReference<LoaderCallback?> = AtomicReference(null),
+    val callbackState: AtomicRef<LoaderCallback?> = AtomicRef(null),
 
     /**
      * Tracks the last accessed(peeked) index on the differ for this generation
       */
-    var lastAccessedIndex: AtomicInteger = AtomicInteger()
+    var lastAccessedIndex: AtomicInt = AtomicInt(0)
 )
 
 internal data class LoaderCallback(
diff --git a/paging/paging-testing/src/androidMain/kotlin/androidx/paging/testing/StaticListPagingSource.kt b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/StaticListPagingSource.kt
similarity index 100%
rename from paging/paging-testing/src/androidMain/kotlin/androidx/paging/testing/StaticListPagingSource.kt
rename to paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/StaticListPagingSource.kt
diff --git a/paging/paging-testing/src/androidMain/kotlin/androidx/paging/testing/StaticListPagingSourceFactory.kt b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/StaticListPagingSourceFactory.kt
similarity index 98%
rename from paging/paging-testing/src/androidMain/kotlin/androidx/paging/testing/StaticListPagingSourceFactory.kt
rename to paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/StaticListPagingSourceFactory.kt
index 000e2a1..fc5ea4d 100644
--- a/paging/paging-testing/src/androidMain/kotlin/androidx/paging/testing/StaticListPagingSourceFactory.kt
+++ b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/StaticListPagingSourceFactory.kt
@@ -22,6 +22,7 @@
 import androidx.paging.Pager
 import androidx.paging.PagingSource
 import androidx.paging.PagingSourceFactory
+import kotlin.jvm.JvmSuppressWildcards
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.launch
diff --git a/paging/paging-testing/src/androidMain/kotlin/androidx/paging/testing/TestPager.kt b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/TestPager.kt
similarity index 99%
rename from paging/paging-testing/src/androidMain/kotlin/androidx/paging/testing/TestPager.kt
rename to paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/TestPager.kt
index 35b14ad..6cca56c 100644
--- a/paging/paging-testing/src/androidMain/kotlin/androidx/paging/testing/TestPager.kt
+++ b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/TestPager.kt
@@ -28,7 +28,8 @@
 import androidx.paging.PagingSource.LoadResult
 import androidx.paging.PagingSource.LoadResult.Page.Companion.COUNT_UNDEFINED
 import androidx.paging.PagingState
-import java.util.concurrent.atomic.AtomicBoolean
+import androidx.paging.testing.internal.AtomicBoolean
+import kotlin.jvm.JvmSuppressWildcards
 import kotlinx.coroutines.sync.Mutex
 import kotlinx.coroutines.sync.withLock
 
diff --git a/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/internal/Atomics.kt b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/internal/Atomics.kt
new file mode 100644
index 0000000..8aed900
--- /dev/null
+++ b/paging/paging-testing/src/commonMain/kotlin/androidx/paging/testing/internal/Atomics.kt
@@ -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.paging.testing.internal
+
+internal expect class AtomicInt(initialValue: Int) {
+    fun get(): Int
+    fun set(value: Int)
+}
+
+internal expect class AtomicBoolean(initialValue: Boolean) {
+    fun get(): Boolean
+    fun set(value: Boolean)
+    fun compareAndSet(expect: Boolean, update: Boolean): Boolean
+}
+
+internal expect class AtomicRef<T>(initialValue: T) {
+    fun get(): T
+    fun set(value: T)
+    fun getAndSet(value: T): T
+}
diff --git a/paging/paging-testing/src/androidUnitTest/kotlin/androidx/paging/testing/PagerFlowSnapshotTest.kt b/paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/PagerFlowSnapshotTest.kt
similarity index 65%
rename from paging/paging-testing/src/androidUnitTest/kotlin/androidx/paging/testing/PagerFlowSnapshotTest.kt
rename to paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/PagerFlowSnapshotTest.kt
index d4bd050..6fd3153 100644
--- a/paging/paging-testing/src/androidUnitTest/kotlin/androidx/paging/testing/PagerFlowSnapshotTest.kt
+++ b/paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/PagerFlowSnapshotTest.kt
@@ -16,6 +16,8 @@
 
 package androidx.paging.testing
 
+import androidx.kruth.assertThat
+import androidx.kruth.assertWithMessage
 import androidx.paging.LoadState
 import androidx.paging.LoadStates
 import androidx.paging.Pager
@@ -27,8 +29,7 @@
 import androidx.paging.PagingState
 import androidx.paging.cachedIn
 import androidx.paging.insertSeparators
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
+import kotlin.test.Test
 import kotlin.test.assertFailsWith
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
@@ -40,39 +41,32 @@
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
 
 @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
-@RunWith(Parameterized::class)
-class PagerFlowSnapshotTest(
-    private val loadDelay: Long
-) {
-    companion object {
-        @Parameterized.Parameters
-        @JvmStatic
-        fun withLoadDelay(): Array<Long> {
-            return arrayOf(0, 10000)
-        }
-    }
-
+class PagerFlowSnapshotTest {
     private val testScope = TestScope(UnconfinedTestDispatcher())
 
-    private fun createFactory(dataFlow: Flow<List<Int>>) = WrappedPagingSourceFactory(
-        dataFlow.asPagingSourceFactory(testScope.backgroundScope),
-        loadDelay
-    )
+    private fun createFactory(dataFlow: Flow<List<Int>>, loadDelay: Long) =
+        WrappedPagingSourceFactory(
+            dataFlow.asPagingSourceFactory(testScope.backgroundScope),
+            loadDelay
+        )
 
-    private fun createSingleGenFactory(data: List<Int>) = WrappedPagingSourceFactory(
-        data.asPagingSourceFactory(),
-        loadDelay
-    )
+    private fun createSingleGenFactory(data: List<Int>, loadDelay: Long) =
+        WrappedPagingSourceFactory(
+            data.asPagingSourceFactory(),
+            loadDelay
+        )
 
     @Test
-    fun initialRefresh() {
+    fun initialRefresh_loadDelay0() = initialRefresh(0)
+
+    @Test
+    fun initialRefresh_loadDelay10000() = initialRefresh(10000)
+
+    private fun initialRefresh(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val pager = createPager(dataFlow)
+        val pager = createPager(dataFlow, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot()
             // first page + prefetched page
@@ -83,9 +77,14 @@
     }
 
     @Test
-    fun initialRefreshSingleGen() {
+    fun initialRefreshSingleGen_loadDelay0() = initialRefreshSingleGen(0)
+
+    @Test
+    fun initialRefreshSingleGen_loadDelay10000() = initialRefreshSingleGen(10000)
+
+    private fun initialRefreshSingleGen(loadDelay: Long) {
         val data = List(30) { it }
-        val pager = createPager(data)
+        val pager = createPager(data, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot()
             // first page + prefetched page
@@ -96,9 +95,15 @@
     }
 
     @Test
-    fun initialRefresh_emptyOperations() {
+    fun initialRefresh_emptyOperations_loadDelay0() = initialRefresh_emptyOperations(0)
+
+    @Test
+    fun initialRefresh_emptyOperations_loadDelay10000() =
+        initialRefresh_emptyOperations(10000)
+
+    private fun initialRefresh_emptyOperations(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val pager = createPager(dataFlow)
+        val pager = createPager(dataFlow, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot {}
             // first page + prefetched page
@@ -109,9 +114,14 @@
     }
 
     @Test
-    fun initialRefresh_withSeparators() {
+    fun initialRefresh_withSeparators_loadDelay0() = initialRefresh_withSeparators(0)
+
+    @Test
+    fun initialRefresh_withSeparators_loadDelay10000() = initialRefresh_withSeparators(10000)
+
+    private fun initialRefresh_withSeparators(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val pager = createPager(dataFlow).map { pagingData ->
+        val pager = createPager(dataFlow, loadDelay).map { pagingData ->
             pagingData.insertSeparators { before: Int?, after: Int? ->
                 if (before != null && after != null) "sep" else null
             }
@@ -126,9 +136,14 @@
     }
 
     @Test
-    fun initialRefresh_withoutPrefetch() {
+    fun initialRefresh_withoutPrefetch_loadDelay0() = initialRefresh_withoutPrefetch(0)
+
+    @Test
+    fun initialRefresh_withoutPrefetch_loadDelay10000() = initialRefresh_withoutPrefetch(10000)
+
+    private fun initialRefresh_withoutPrefetch(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val pager = createPagerNoPrefetch(dataFlow)
+        val pager = createPagerNoPrefetch(dataFlow, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot()
 
@@ -139,9 +154,14 @@
     }
 
     @Test
-    fun initialRefresh_withInitialKey() {
+    fun initialRefresh_withInitialKey_loadDelay0() = initialRefresh_withInitialKey(0)
+
+    @Test
+    fun initialRefresh_withInitialKey_loadDelay10000() = initialRefresh_withInitialKey(10000)
+
+    private fun initialRefresh_withInitialKey(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val pager = createPager(dataFlow, 10)
+        val pager = createPager(dataFlow, loadDelay, 10)
         testScope.runTest {
             val snapshot = pager.asSnapshot()
 
@@ -152,9 +172,16 @@
     }
 
     @Test
-    fun initialRefresh_withInitialKey_withoutPrefetch() {
+    fun initialRefresh_withInitialKey_withoutPrefetch_loadDelay0() =
+        initialRefresh_withInitialKey_withoutPrefetch(0)
+
+    @Test
+    fun initialRefresh_withInitialKey_withoutPrefetch_loadDelay10000() =
+        initialRefresh_withInitialKey_withoutPrefetch(10000)
+
+    private fun initialRefresh_withInitialKey_withoutPrefetch(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val pager = createPagerNoPrefetch(dataFlow, 10)
+        val pager = createPagerNoPrefetch(dataFlow, loadDelay, 10)
         testScope.runTest {
             val snapshot = pager.asSnapshot()
 
@@ -195,9 +222,14 @@
     }
 
     @Test
-    fun emptyInitialRefresh() {
+    fun emptyInitialRefresh_loadDelay0() = emptyInitialRefresh(0)
+
+    @Test
+    fun emptyInitialRefresh_loadDelay10000() = emptyInitialRefresh(10000)
+
+    private fun emptyInitialRefresh(loadDelay: Long) {
         val dataFlow = emptyFlow<List<Int>>()
-        val pager = createPager(dataFlow)
+        val pager = createPager(dataFlow, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot()
 
@@ -208,9 +240,14 @@
     }
 
     @Test
-    fun emptyInitialRefreshSingleGen() {
+    fun emptyInitialRefreshSingleGen_loadDelay0() = emptyInitialRefreshSingleGen(0)
+
+    @Test
+    fun emptyInitialRefreshSingleGen_loadDelay10000() = emptyInitialRefreshSingleGen(10000)
+
+    private fun emptyInitialRefreshSingleGen(loadDelay: Long) {
         val data = emptyList<Int>()
-        val pager = createPager(data)
+        val pager = createPager(data, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot()
 
@@ -221,9 +258,15 @@
     }
 
     @Test
-    fun emptyInitialRefresh_emptyOperations() {
+    fun emptyInitialRefresh_emptyOperations_loadDelay0() = emptyInitialRefresh_emptyOperations(0)
+
+    @Test
+    fun emptyInitialRefresh_emptyOperations_loadDelay10000() =
+        emptyInitialRefresh_emptyOperations(10000)
+
+    private fun emptyInitialRefresh_emptyOperations(loadDelay: Long) {
         val dataFlow = emptyFlow<List<Int>>()
-        val pager = createPager(dataFlow)
+        val pager = createPager(dataFlow, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot()
 
@@ -234,9 +277,14 @@
     }
 
     @Test
-    fun manualRefresh() {
+    fun manualRefresh_loadDelay0() = manualRefresh(0)
+
+    @Test
+    fun manualRefresh_loadDelay10000() = manualRefresh(10000)
+
+    private fun manualRefresh(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val pager = createPagerNoPrefetch(dataFlow).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPrefetch(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 refresh()
@@ -248,9 +296,14 @@
     }
 
     @Test
-    fun manualRefreshSingleGen() {
+    fun manualRefreshSingleGen_loadDelay0() = manualRefreshSingleGen(0)
+
+    @Test
+    fun manualRefreshSingleGen_loadDelay10000() = manualRefreshSingleGen(10000)
+
+    private fun manualRefreshSingleGen(loadDelay: Long) {
         val data = List(30) { it }
-        val pager = createPager(data)
+        val pager = createPager(data, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 refresh()
@@ -313,9 +366,14 @@
     }
 
     @Test
-    fun manualEmptyRefresh() {
+    fun manualEmptyRefresh_loadDelay0() = manualEmptyRefresh(0)
+
+    @Test
+    fun manualEmptyRefresh_loadDelay10000() = manualEmptyRefresh(10000)
+
+    private fun manualEmptyRefresh(loadDelay: Long) {
         val dataFlow = emptyFlow<List<Int>>()
-        val pager = createPagerNoPrefetch(dataFlow)
+        val pager = createPagerNoPrefetch(dataFlow, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 refresh()
@@ -327,9 +385,14 @@
     }
 
     @Test
-    fun appendWhile() {
+    fun appendWhile_loadDelay0() = appendWhile(0)
+
+    @Test
+    fun appendWhile_loadDelay10000() = appendWhile(10000)
+
+    private fun appendWhile(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val pager = createPager(dataFlow)
+        val pager = createPager(dataFlow, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 appendScrollWhile { item: Int ->
@@ -347,9 +410,14 @@
     }
 
     @Test
-    fun appendWhile_withDrops() {
+    fun appendWhile_withDrops_loadDelay0() = appendWhile_withDrops(0)
+
+    @Test
+    fun appendWhile_withDrops_loadDelay10000() = appendWhile_withDrops(10000)
+
+    private fun appendWhile_withDrops(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val pager = createPagerWithDrops(dataFlow)
+        val pager = createPagerWithDrops(dataFlow, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 appendScrollWhile { item ->
@@ -364,9 +432,14 @@
     }
 
     @Test
-    fun appendWhile_withSeparators() {
+    fun appendWhile_withSeparators_loadDelay0() = appendWhile_withSeparators(0)
+
+    @Test
+    fun appendWhile_withSeparators_loadDelay10000() = appendWhile_withSeparators(10000)
+
+    private fun appendWhile_withSeparators(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val pager = createPager(dataFlow).map { pagingData ->
+        val pager = createPager(dataFlow, loadDelay).map { pagingData ->
             pagingData.insertSeparators { before: Int?, _ ->
                 if (before == 9 || before == 12) "sep" else null
             }
@@ -391,9 +464,14 @@
     }
 
     @Test
-    fun appendWhile_withoutPrefetch() {
+    fun appendWhile_withoutPrefetch_loadDelay0() = appendWhile_withoutPrefetch(0)
+
+    @Test
+    fun appendWhile_withoutPrefetch_loadDelay10000() = appendWhile_withoutPrefetch(10000)
+
+    private fun appendWhile_withoutPrefetch(loadDelay: Long) {
         val dataFlow = flowOf(List(50) { it })
-        val pager = createPagerNoPrefetch(dataFlow)
+        val pager = createPagerNoPrefetch(dataFlow, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 appendScrollWhile { item: Int ->
@@ -409,9 +487,14 @@
     }
 
     @Test
-    fun appendWhile_withoutPlaceholders() {
+    fun appendWhile_withoutPlaceholders_loadDelay0() = appendWhile_withoutPlaceholders(0)
+
+    @Test
+    fun appendWhile_withoutPlaceholders_loadDelay10000() = appendWhile_withoutPlaceholders(10000)
+
+    private fun appendWhile_withoutPlaceholders(loadDelay: Long) {
         val dataFlow = flowOf(List(50) { it })
-        val pager = createPagerNoPlaceholders(dataFlow)
+        val pager = createPagerNoPlaceholders(dataFlow, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 appendScrollWhile { item: Int ->
@@ -429,9 +512,14 @@
     }
 
     @Test
-    fun prependWhile() {
+    fun prependWhile_loadDelay0() = prependWhile(0)
+
+    @Test
+    fun prependWhile_loadDelay10000() = prependWhile(10000)
+
+    private fun prependWhile(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val pager = createPager(dataFlow, 20)
+        val pager = createPager(dataFlow, loadDelay, 20)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 prependScrollWhile { item: Int ->
@@ -449,9 +537,14 @@
     }
 
     @Test
-    fun prependWhile_withDrops() {
+    fun prependWhile_withDrops_loadDelay0() = prependWhile_withDrops(0)
+
+    @Test
+    fun prependWhile_withDrops_loadDelay10000() = prependWhile_withDrops(10000)
+
+    private fun prependWhile_withDrops(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val pager = createPagerWithDrops(dataFlow, 20)
+        val pager = createPagerWithDrops(dataFlow, loadDelay, 20)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 prependScrollWhile { item: Int ->
@@ -466,9 +559,14 @@
     }
 
     @Test
-    fun prependWhile_withSeparators() {
+    fun prependWhile_withSeparators_loadDelay0() = prependWhile_withSeparators(0)
+
+    @Test
+    fun prependWhile_withSeparators_loadDelay10000() = prependWhile_withSeparators(10000)
+
+    private fun prependWhile_withSeparators(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val pager = createPager(dataFlow, 20).map { pagingData ->
+        val pager = createPager(dataFlow, loadDelay, 20).map { pagingData ->
             pagingData.insertSeparators { before: Int?, _ ->
                 if (before == 14 || before == 18) "sep" else null
             }
@@ -493,9 +591,14 @@
     }
 
     @Test
-    fun prependWhile_withoutPrefetch() {
+    fun prependWhile_withoutPrefetch_loadDelay0() = prependWhile_withoutPrefetch(0)
+
+    @Test
+    fun prependWhile_withoutPrefetch_loadDelay10000() = prependWhile_withoutPrefetch(10000)
+
+    private fun prependWhile_withoutPrefetch(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val pager = createPagerNoPrefetch(dataFlow, 20)
+        val pager = createPagerNoPrefetch(dataFlow, loadDelay, 20)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 prependScrollWhile { item: Int ->
@@ -511,9 +614,14 @@
     }
 
     @Test
-    fun prependWhile_withoutPlaceholders() {
+    fun prependWhile_withoutPlaceholders_loadDelay0() = prependWhile_withoutPlaceholders(0)
+
+    @Test
+    fun prependWhile_withoutPlaceholders_loadDelay10000() = prependWhile_withoutPlaceholders(10000)
+
+    private fun prependWhile_withoutPlaceholders(loadDelay: Long) {
         val dataFlow = flowOf(List(50) { it })
-        val pager = createPagerNoPlaceholders(dataFlow, 30)
+        val pager = createPagerNoPlaceholders(dataFlow, loadDelay, 30)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 prependScrollWhile { item: Int ->
@@ -533,9 +641,14 @@
     }
 
     @Test
-    fun appendWhile_withInitialKey() {
+    fun appendWhile_withInitialKey_loadDelay0() = appendWhile_withInitialKey(0)
+
+    @Test
+    fun appendWhile_withInitialKey_loadDelay10000() = appendWhile_withInitialKey(10000)
+
+    private fun appendWhile_withInitialKey(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val pager = createPager(dataFlow, 10)
+        val pager = createPager(dataFlow, loadDelay, 10)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 appendScrollWhile { item: Int ->
@@ -553,9 +666,16 @@
     }
 
     @Test
-    fun appendWhile_withInitialKey_withoutPlaceholders() {
+    fun appendWhile_withInitialKey_withoutPlaceholders_loadDelay0() =
+        appendWhile_withInitialKey_withoutPlaceholders(0)
+
+    @Test
+    fun appendWhile_withInitialKey_withoutPlaceholders_loadDelay10000() =
+        appendWhile_withInitialKey_withoutPlaceholders(10000)
+
+    private fun appendWhile_withInitialKey_withoutPlaceholders(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val pager = createPagerNoPlaceholders(dataFlow, 10)
+        val pager = createPagerNoPlaceholders(dataFlow, loadDelay, 10)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 appendScrollWhile { item: Int ->
@@ -573,9 +693,16 @@
     }
 
     @Test
-    fun appendWhile_withInitialKey_withoutPrefetch() {
+    fun appendWhile_withInitialKey_withoutPrefetch_loadDelay0() =
+        appendWhile_withInitialKey_withoutPrefetch(0)
+
+    @Test
+    fun appendWhile_withInitialKey_withoutPrefetch_loadDelay10000() =
+        appendWhile_withInitialKey_withoutPrefetch(10000)
+
+    private fun appendWhile_withInitialKey_withoutPrefetch(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val pager = createPagerNoPrefetch(dataFlow, 10)
+        val pager = createPagerNoPrefetch(dataFlow, loadDelay, 10)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 appendScrollWhile { item: Int ->
@@ -591,9 +718,14 @@
     }
 
     @Test
-    fun prependWhile_withoutInitialKey() {
+    fun prependWhile_withoutInitialKey_loadDelay0() = prependWhile_withoutInitialKey(0)
+
+    @Test
+    fun prependWhile_withoutInitialKey_loadDelay10000() = prependWhile_withoutInitialKey(10000)
+
+    private fun prependWhile_withoutInitialKey(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val pager = createPager(dataFlow)
+        val pager = createPager(dataFlow, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 prependScrollWhile { item: Int ->
@@ -609,9 +741,14 @@
     }
 
     @Test
-    fun consecutiveAppendWhile() {
+    fun consecutiveAppendWhile_loadDelay0() = consecutiveAppendWhile(0)
+
+    @Test
+    fun consecutiveAppendWhile_loadDelay10000() = consecutiveAppendWhile(10000)
+
+    private fun consecutiveAppendWhile(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val pager = createPager(dataFlow).cachedIn(testScope.backgroundScope)
+        val pager = createPager(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot1 = pager.asSnapshot {
                 appendScrollWhile { item: Int ->
@@ -638,9 +775,15 @@
     }
 
     @Test
-    fun consecutivePrependWhile() {
+    fun consecutivePrependWhile_loadDelay0() = consecutivePrependWhile(0)
+
+    @Test
+    fun consecutivePrependWhile_loadDelay10000() = consecutivePrependWhile(10000)
+
+    private fun consecutivePrependWhile(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val pager = createPagerNoPrefetch(dataFlow, 20).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPrefetch(dataFlow, loadDelay, 20)
+            .cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot1 = pager.asSnapshot {
                 prependScrollWhile { item: Int ->
@@ -662,9 +805,16 @@
     }
 
     @Test
-    fun appendWhile_outOfBounds_returnsCurrentlyLoadedItems() {
+    fun appendWhile_outOfBounds_returnsCurrentlyLoadedItems_loadDelay0() =
+        appendWhile_outOfBounds_returnsCurrentlyLoadedItems(0)
+
+    @Test
+    fun appendWhile_outOfBounds_returnsCurrentlyLoadedItems_loadDelay10000() =
+        appendWhile_outOfBounds_returnsCurrentlyLoadedItems(10000)
+
+    private fun appendWhile_outOfBounds_returnsCurrentlyLoadedItems(loadDelay: Long) {
         val dataFlow = flowOf(List(10) { it })
-        val pager = createPager(dataFlow)
+        val pager = createPager(dataFlow, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 appendScrollWhile { item: Int ->
@@ -681,9 +831,16 @@
     }
 
     @Test
-    fun prependWhile_outOfBounds_returnsCurrentlyLoadedItems() {
+    fun prependWhile_outOfBounds_returnsCurrentlyLoadedItems_loadDelay0() =
+        prependWhile_outOfBounds_returnsCurrentlyLoadedItems(0)
+
+    @Test
+    fun prependWhile_outOfBounds_returnsCurrentlyLoadedItems_loadDelay10000() =
+        prependWhile_outOfBounds_returnsCurrentlyLoadedItems(10000)
+
+    private fun prependWhile_outOfBounds_returnsCurrentlyLoadedItems(loadDelay: Long) {
         val dataFlow = flowOf(List(20) { it })
-        val pager = createPager(dataFlow, 10)
+        val pager = createPager(dataFlow, loadDelay, 10)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 prependScrollWhile { item: Int ->
@@ -702,9 +859,14 @@
     }
 
     @Test
-    fun refreshAndAppendWhile() {
+    fun refreshAndAppendWhile_loadDelay0() = refreshAndAppendWhile(0)
+
+    @Test
+    fun refreshAndAppendWhile_loadDelay10000() = refreshAndAppendWhile(10000)
+
+    private fun refreshAndAppendWhile(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val pager = createPager(dataFlow).cachedIn(testScope.backgroundScope)
+        val pager = createPager(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 refresh() // triggers second gen
@@ -719,9 +881,14 @@
     }
 
     @Test
-    fun refreshAndPrependWhile() {
+    fun refreshAndPrependWhile_loadDelay0() = refreshAndPrependWhile(0)
+
+    @Test
+    fun refreshAndPrependWhile_loadDelay10000() = refreshAndPrependWhile(10000)
+
+    private fun refreshAndPrependWhile(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val pager = createPager(dataFlow, 20).cachedIn(testScope.backgroundScope)
+        val pager = createPager(dataFlow, loadDelay, 20).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 // this prependScrollWhile does not cause paging to load more items
@@ -746,9 +913,14 @@
     }
 
     @Test
-    fun appendWhileAndRefresh() {
+    fun appendWhileAndRefresh_loadDelay0() = appendWhileAndRefresh(0)
+
+    @Test
+    fun appendWhileAndRefresh_loadDelay10000() = appendWhileAndRefresh(10000)
+
+    private fun appendWhileAndRefresh(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val pager = createPager(dataFlow).cachedIn(testScope.backgroundScope)
+        val pager = createPager(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 appendScrollWhile { item: Int ->
@@ -766,9 +938,14 @@
     }
 
     @Test
-    fun prependWhileAndRefresh() {
+    fun prependWhileAndRefresh_loadDelay0() = prependWhileAndRefresh(0)
+
+    @Test
+    fun prependWhileAndRefresh_loadDelay10000() = prependWhileAndRefresh(10000)
+
+    private fun prependWhileAndRefresh(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val pager = createPager(dataFlow, 15).cachedIn(testScope.backgroundScope)
+        val pager = createPager(dataFlow, loadDelay, 15).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 prependScrollWhile { item: Int ->
@@ -786,19 +963,23 @@
     }
 
     @Test
-    fun consecutiveGenerations_fromFlow() {
-        val loadDelay = 500 + loadDelay
+    fun consecutiveGenerations_fromFlow_loadDelay0() = consecutiveGenerations_fromFlow(0)
+
+    @Test
+    fun consecutiveGenerations_fromFlow_loadDelay10000() = consecutiveGenerations_fromFlow(10000)
+
+    private fun consecutiveGenerations_fromFlow(loadDelay: Long) {
         // wait for 500 + loadDelay between each emission
         val dataFlow = flow {
             emit(emptyList())
-            delay(loadDelay)
+            delay(500 + loadDelay)
 
             emit(List(30) { it })
-            delay(loadDelay)
+            delay(500 + loadDelay)
 
             emit(List(30) { it + 30 })
         }
-        val pager = createPagerNoPrefetch(dataFlow).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPrefetch(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot1 = pager.asSnapshot()
             assertThat(snapshot1).containsExactlyElementsIn(
@@ -822,15 +1003,21 @@
     }
 
     @Test
-    fun consecutiveGenerations_PagingDataFrom_withoutLoadStates() {
-        val loadDelay = 500 + loadDelay
+    fun consecutiveGenerations_PagingDataFrom_withoutLoadStates_loadDelay0() =
+        consecutiveGenerations_PagingDataFrom_withoutLoadStates(0)
+
+    @Test
+    fun consecutiveGenerations_PagingDataFrom_withoutLoadStates_loadDelay10000() =
+        consecutiveGenerations_PagingDataFrom_withoutLoadStates(10000)
+
+    private fun consecutiveGenerations_PagingDataFrom_withoutLoadStates(loadDelay: Long) {
         // wait for 500 + loadDelay between each emission
         val pager = flow {
             emit(PagingData.empty())
-            delay(loadDelay)
+            delay(500 + loadDelay)
 
             emit(PagingData.from(List(10) { it }))
-            delay(loadDelay)
+            delay(500 + loadDelay)
 
             emit(PagingData.from(List(10) { it + 30 }))
         }
@@ -844,8 +1031,14 @@
     }
 
     @Test
-    fun consecutiveGenerations_PagingDataFrom_withLoadStates() {
-        val loadDelay = 500 + loadDelay
+    fun consecutiveGenerations_PagingDataFrom_withLoadStates_loadDelay0() =
+        consecutiveGenerations_PagingDataFrom_withLoadStates(0)
+
+    @Test
+    fun consecutiveGenerations_PagingDataFrom_withLoadStates_loadDelay10000() =
+        consecutiveGenerations_PagingDataFrom_withLoadStates(10000)
+
+    private fun consecutiveGenerations_PagingDataFrom_withLoadStates(loadDelay: Long) {
         // wait for 500 + loadDelay between each emission
         val pager = flow {
             emit(PagingData.empty(LoadStates(
@@ -853,14 +1046,14 @@
                 prepend = LoadState.NotLoading(true),
                 append = LoadState.NotLoading(true)
             )))
-            delay(loadDelay)
+            delay(500 + loadDelay)
 
             emit(PagingData.from(List(10) { it }, LoadStates(
                 refresh = LoadState.NotLoading(true),
                 prepend = LoadState.NotLoading(true),
                 append = LoadState.NotLoading(true)
             )))
-            delay(loadDelay)
+            delay(500 + loadDelay)
 
             emit(PagingData.from(List(10) { it + 30 }, LoadStates(
                 refresh = LoadState.NotLoading(true),
@@ -874,14 +1067,14 @@
                 emptyList<Int>()
             )
 
-            delay(loadDelay)
+            delay(500 + loadDelay)
 
             val snapshot2 = pager.asSnapshot()
             assertThat(snapshot2).containsExactlyElementsIn(
                 listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
             )
 
-            delay(loadDelay)
+            delay(500 + loadDelay)
 
             val snapshot3 = pager.asSnapshot()
             assertThat(snapshot3).containsExactlyElementsIn(
@@ -891,9 +1084,16 @@
     }
 
     @Test
-    fun consecutiveGenerations_fromSharedFlow_emitAfterRefresh() {
+    fun consecutiveGenerations_fromSharedFlow_emitAfterRefresh_loadDelay0() =
+        consecutiveGenerations_fromSharedFlow_emitAfterRefresh(0)
+
+    @Test
+    fun consecutiveGenerations_fromSharedFlow_emitAfterRefresh_loadDelay10000() =
+        consecutiveGenerations_fromSharedFlow_emitAfterRefresh(10000)
+
+    private fun consecutiveGenerations_fromSharedFlow_emitAfterRefresh(loadDelay: Long) {
         val dataFlow = MutableSharedFlow<List<Int>>()
-        val pager = createPagerNoPrefetch(dataFlow).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPrefetch(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot1 = pager.asSnapshot()
             assertThat(snapshot1).containsExactlyElementsIn(
@@ -917,9 +1117,16 @@
     }
 
     @Test
-    fun consecutiveGenerations_fromSharedFlow_emitBeforeRefresh() {
+    fun consecutiveGenerations_fromSharedFlow_emitBeforeRefresh_loadDelay0() =
+        consecutiveGenerations_fromSharedFlow_emitBeforeRefresh(0)
+
+    @Test
+    fun consecutiveGenerations_fromSharedFlow_emitBeforeRefresh_loadDelay10000() =
+        consecutiveGenerations_fromSharedFlow_emitBeforeRefresh(10000)
+
+    private fun consecutiveGenerations_fromSharedFlow_emitBeforeRefresh(loadDelay: Long) {
         val dataFlow = MutableSharedFlow<List<Int>>()
-        val pager = createPagerNoPrefetch(dataFlow).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPrefetch(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             dataFlow.emit(emptyList())
             val snapshot1 = pager.asSnapshot()
@@ -942,17 +1149,23 @@
     }
 
     @Test
-    fun consecutiveGenerations_nonNullRefreshKey() {
-        val loadDelay = 500 + loadDelay
+    fun consecutiveGenerations_nonNullRefreshKey_loadDelay0() =
+        consecutiveGenerations_nonNullRefreshKey(0)
+
+    @Test
+    fun consecutiveGenerations_nonNullRefreshKey_loadDelay10000() =
+        consecutiveGenerations_nonNullRefreshKey(10000)
+
+    private fun consecutiveGenerations_nonNullRefreshKey(loadDelay: Long) {
         val dataFlow = flow {
             // first gen
             emit(List(20) { it })
             // wait for refresh + append
-            delay(loadDelay * 2)
+            delay((500 + loadDelay) * 2)
             // second gen
             emit(List(20) { it })
         }
-        val pager = createPagerNoPrefetch(dataFlow).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPrefetch(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot1 = pager.asSnapshot {
                 // we scroll to register a non-null anchorPos
@@ -974,17 +1187,24 @@
     }
 
     @Test
-    fun consecutiveGenerations_withInitialKey_nullRefreshKey() {
-        val loadDelay = 500 + loadDelay
+    fun consecutiveGenerations_withInitialKey_nullRefreshKey_loadDelay0() =
+        consecutiveGenerations_withInitialKey_nullRefreshKey(0)
+
+    @Test
+    fun consecutiveGenerations_withInitialKey_nullRefreshKey_loadDelay10000() =
+        consecutiveGenerations_withInitialKey_nullRefreshKey(10000)
+
+    private fun consecutiveGenerations_withInitialKey_nullRefreshKey(loadDelay: Long) {
         // wait for 500 + loadDelay between each emission
         val dataFlow = flow {
             // first gen
             emit(List(20) { it })
-            delay(loadDelay)
+            delay(500 + loadDelay)
             // second gen
             emit(List(20) { it })
         }
-        val pager = createPagerNoPrefetch(dataFlow, 10).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPrefetch(dataFlow, loadDelay, 10)
+            .cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot1 = pager.asSnapshot()
             assertThat(snapshot1).containsExactlyElementsIn(
@@ -1000,17 +1220,24 @@
     }
 
     @Test
-    fun consecutiveGenerations_withInitialKey_nonNullRefreshKey() {
-        val loadDelay = 500 + loadDelay
+    fun consecutiveGenerations_withInitialKey_nonNullRefreshKey_loadDelay0() =
+        consecutiveGenerations_withInitialKey_nonNullRefreshKey(0)
+
+    @Test
+    fun consecutiveGenerations_withInitialKey_nonNullRefreshKey_loadDelay10000() =
+        consecutiveGenerations_withInitialKey_nonNullRefreshKey(10000)
+
+    private fun consecutiveGenerations_withInitialKey_nonNullRefreshKey(loadDelay: Long) {
         val dataFlow = flow {
             // first gen
             emit(List(20) { it })
             // wait for refresh + append
-            delay(loadDelay * 2)
+            delay((500 + loadDelay) * 2)
             // second gen
             emit(List(20) { it })
         }
-        val pager = createPagerNoPrefetch(dataFlow, 10).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPrefetch(dataFlow, loadDelay, 10)
+            .cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot1 = pager.asSnapshot {
                 // we scroll to register a non-null anchorPos
@@ -1032,9 +1259,14 @@
     }
 
     @Test
-    fun prependScroll() {
+    fun prependScroll_loadDelay0() = prependScroll(0)
+
+    @Test
+    fun prependScroll_loadDelay10000() = prependScroll(10000)
+
+    private fun prependScroll(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPager(dataFlow, 50)
+        val pager = createPager(dataFlow, loadDelay, 50)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 scrollTo(42)
@@ -1052,9 +1284,14 @@
     }
 
     @Test
-    fun prependScroll_withDrops() {
+    fun prependScroll_withDrops_loadDelay0() = prependScroll_withDrops(0)
+
+    @Test
+    fun prependScroll_withDrops_loadDelay10000() = prependScroll_withDrops(10000)
+
+    private fun prependScroll_withDrops(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerWithDrops(dataFlow, 50)
+        val pager = createPagerWithDrops(dataFlow, loadDelay, 50)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 scrollTo(42)
@@ -1067,9 +1304,14 @@
     }
 
     @Test
-    fun prependScroll_withSeparators() {
+    fun prependScroll_withSeparators_loadDelay0() = prependScroll_withSeparators(0)
+
+    @Test
+    fun prependScroll_withSeparators_loadDelay10000() = prependScroll_withSeparators(10000)
+
+    private fun prependScroll_withSeparators(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPager(dataFlow, 50).map { pagingData ->
+        val pager = createPager(dataFlow, loadDelay, 50).map { pagingData ->
             pagingData.insertSeparators { before: Int?, _ ->
                 if (before == 42 || before == 49) "sep" else null
             }
@@ -1092,9 +1334,14 @@
     }
 
     @Test
-    fun consecutivePrependScroll() {
+    fun consecutivePrependScroll_loadDelay0() = consecutivePrependScroll(0)
+
+    @Test
+    fun consecutivePrependScroll_loadDelay10000() = consecutivePrependScroll(10000)
+
+    private fun consecutivePrependScroll(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPager(dataFlow, 50)
+        val pager = createPager(dataFlow, loadDelay, 50)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 scrollTo(42)
@@ -1114,9 +1361,16 @@
     }
 
     @Test
-    fun consecutivePrependScroll_multiSnapshots() {
+    fun consecutivePrependScroll_multiSnapshots_loadDelay0() =
+        consecutivePrependScroll_multiSnapshots(0)
+
+    @Test
+    fun consecutivePrependScroll_multiSnapshots_loadDelay10000() =
+        consecutivePrependScroll_multiSnapshots(10000)
+
+    private fun consecutivePrependScroll_multiSnapshots(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPager(dataFlow, 50)
+        val pager = createPager(dataFlow, loadDelay, 50)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 scrollTo(42)
@@ -1145,9 +1399,14 @@
     }
 
     @Test
-    fun prependScroll_indexOutOfBounds() {
+    fun prependScroll_indexOutOfBounds_loadDelay0() = prependScroll_indexOutOfBounds(0)
+
+    @Test
+    fun prependScroll_indexOutOfBounds_loadDelay10000() = prependScroll_indexOutOfBounds(10000)
+
+    private fun prependScroll_indexOutOfBounds(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPager(dataFlow, 5).cachedIn(testScope.backgroundScope)
+        val pager = createPager(dataFlow, loadDelay, 5).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 scrollTo(-5)
@@ -1163,9 +1422,14 @@
     }
 
     @Test
-    fun prependScroll_accessPageBoundary() {
+    fun prependScroll_accessPageBoundary_loadDelay0() = prependScroll_accessPageBoundary(0)
+
+    @Test
+    fun prependScroll_accessPageBoundary_loadDelay10000() = prependScroll_accessPageBoundary(10000)
+
+    private fun prependScroll_accessPageBoundary(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPager(dataFlow, 50).cachedIn(testScope.backgroundScope)
+        val pager = createPager(dataFlow, loadDelay, 50).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 scrollTo(47)
@@ -1181,9 +1445,14 @@
     }
 
     @Test
-    fun prependScroll_withoutPrefetch() {
+    fun prependScroll_withoutPrefetch_loadDelay0() = prependScroll_withoutPrefetch(0)
+
+    @Test
+    fun prependScroll_withoutPrefetch_loadDelay10000() = prependScroll_withoutPrefetch(10000)
+
+    private fun prependScroll_withoutPrefetch(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerNoPrefetch(dataFlow, 50)
+        val pager = createPagerNoPrefetch(dataFlow, loadDelay, 50)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 scrollTo(42)
@@ -1197,9 +1466,16 @@
     }
 
     @Test
-    fun prependScroll_withoutPlaceholders() {
+    fun prependScroll_withoutPlaceholders_loadDelay0() = prependScroll_withoutPlaceholders(0)
+
+    @Test
+    fun prependScroll_withoutPlaceholders_loadDelay10000() =
+        prependScroll_withoutPlaceholders(10000)
+
+    private fun prependScroll_withoutPlaceholders(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerNoPlaceholders(dataFlow, 50).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPlaceholders(dataFlow, loadDelay, 50)
+            .cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 // Without placeholders, first loaded page always starts at index[0]
@@ -1215,9 +1491,17 @@
     }
 
     @Test
-    fun prependScroll_withoutPlaceholders_indexOutOfBounds() {
+    fun prependScroll_withoutPlaceholders_indexOutOfBounds_loadDelay0() =
+        prependScroll_withoutPlaceholders_indexOutOfBounds(0)
+
+    @Test
+    fun prependScroll_withoutPlaceholders_indexOutOfBounds_loadDelay10000() =
+        prependScroll_withoutPlaceholders_indexOutOfBounds(10000)
+
+    private fun prependScroll_withoutPlaceholders_indexOutOfBounds(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerNoPlaceholders(dataFlow, 50).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPlaceholders(dataFlow, loadDelay, 50)
+            .cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 scrollTo(-5)
@@ -1236,9 +1520,17 @@
     }
 
     @Test
-    fun prependScroll_withoutPlaceholders_indexOutOfBoundsIsCapped() {
+    fun prependScroll_withoutPlaceholders_indexOutOfBoundsIsCapped_loadDelay0() =
+        prependScroll_withoutPlaceholders_indexOutOfBoundsIsCapped(0)
+
+    @Test
+    fun prependScroll_withoutPlaceholders_indexOutOfBoundsIsCapped_loadDelay10000() =
+        prependScroll_withoutPlaceholders_indexOutOfBoundsIsCapped(10000)
+
+    private fun prependScroll_withoutPlaceholders_indexOutOfBoundsIsCapped(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerNoPlaceholders(dataFlow, 5).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPlaceholders(dataFlow, loadDelay, 5)
+            .cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 scrollTo(-5)
@@ -1254,9 +1546,17 @@
     }
 
     @Test
-    fun consecutivePrependScroll_withoutPlaceholders() {
+    fun consecutivePrependScroll_withoutPlaceholders_loadDelay0() =
+        consecutivePrependScroll_withoutPlaceholders(0)
+
+    @Test
+    fun consecutivePrependScroll_withoutPlaceholders_loadDelay10000() =
+        consecutivePrependScroll_withoutPlaceholders(10000)
+
+    private fun consecutivePrependScroll_withoutPlaceholders(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerNoPlaceholders(dataFlow, 50).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPlaceholders(dataFlow, loadDelay, 50)
+            .cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 // Without placeholders, first loaded page always starts at index[0]
@@ -1280,9 +1580,17 @@
     }
 
     @Test
-    fun consecutivePrependScroll_withoutPlaceholders_multiSnapshot() {
+    fun consecutivePrependScroll_withoutPlaceholders_multiSnapshot_loadDelay0() =
+        consecutivePrependScroll_withoutPlaceholders_multiSnapshot(0)
+
+    @Test
+    fun consecutivePrependScroll_withoutPlaceholders_multiSnapshot_loadDelay10000() =
+        consecutivePrependScroll_withoutPlaceholders_multiSnapshot(10000)
+
+    private fun consecutivePrependScroll_withoutPlaceholders_multiSnapshot(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerNoPlaceholders(dataFlow, 50).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPlaceholders(dataFlow, loadDelay, 50)
+            .cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 // Without placeholders, first loaded page always starts at index[0]
@@ -1312,7 +1620,14 @@
     }
 
     @Test
-    fun prependScroll_withoutPlaceholders_noPrefetchTriggered() {
+    fun prependScroll_withoutPlaceholders_noPrefetchTriggered_loadDelay0() =
+        prependScroll_withoutPlaceholders_noPrefetchTriggered(0)
+
+    @Test
+    fun prependScroll_withoutPlaceholders_noPrefetchTriggered_loadDelay10000() =
+        prependScroll_withoutPlaceholders_noPrefetchTriggered(10000)
+
+    private fun prependScroll_withoutPlaceholders_noPrefetchTriggered(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
         val pager = Pager(
             config = PagingConfig(
@@ -1323,7 +1638,7 @@
                 prefetchDistance = 1
             ),
             initialKey = 50,
-            pagingSourceFactory = createFactory(dataFlow),
+            pagingSourceFactory = createFactory(dataFlow, loadDelay),
         ).flow.cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
@@ -1340,9 +1655,14 @@
     }
 
     @Test
-    fun appendScroll() {
+    fun appendScroll_loadDelay0() = appendScroll(0)
+
+    @Test
+    fun appendScroll_loadDelay10000() = appendScroll(10000)
+
+    private fun appendScroll(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPager(dataFlow)
+        val pager = createPager(dataFlow, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 scrollTo(12)
@@ -1358,9 +1678,14 @@
     }
 
     @Test
-    fun appendScroll_withDrops() {
+    fun appendScroll_withDrops_loadDelay0() = appendScroll_withDrops(0)
+
+    @Test
+    fun appendScroll_withDrops_loadDelay10000() = appendScroll_withDrops(10000)
+
+    private fun appendScroll_withDrops(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerWithDrops(dataFlow)
+        val pager = createPagerWithDrops(dataFlow, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 scrollTo(12)
@@ -1373,9 +1698,14 @@
     }
 
     @Test
-    fun appendScroll_withSeparators() {
+    fun appendScroll_withSeparators_loadDelay0() = appendScroll_withSeparators(0)
+
+    @Test
+    fun appendScroll_withSeparators_loadDelay10000() = appendScroll_withSeparators(10000)
+
+    private fun appendScroll_withSeparators(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPager(dataFlow).map { pagingData ->
+        val pager = createPager(dataFlow, loadDelay).map { pagingData ->
             pagingData.insertSeparators { before: Int?, _ ->
                 if (before == 0 || before == 14) "sep" else null
             }
@@ -1394,10 +1724,15 @@
         }
     }
 
+        @Test
+    fun consecutiveAppendScroll_loadDelay0() = consecutiveAppendScroll(0)
+
     @Test
-    fun consecutiveAppendScroll() {
+    fun consecutiveAppendScroll_loadDelay10000() = consecutiveAppendScroll(10000)
+
+    private fun consecutiveAppendScroll(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPager(dataFlow)
+        val pager = createPager(dataFlow, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 scrollTo(12)
@@ -1414,10 +1749,17 @@
         }
     }
 
+        @Test
+    fun consecutiveAppendScroll_multiSnapshots_loadDelay0() =
+        consecutiveAppendScroll_multiSnapshots(0)
+
     @Test
-    fun consecutiveAppendScroll_multiSnapshots() {
+    fun consecutiveAppendScroll_multiSnapshots_loadDelay10000() =
+        consecutiveAppendScroll_multiSnapshots(10000)
+
+    private fun consecutiveAppendScroll_multiSnapshots(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPager(dataFlow)
+        val pager = createPager(dataFlow, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 scrollTo(12)
@@ -1444,9 +1786,14 @@
     }
 
     @Test
-    fun appendScroll_indexOutOfBounds() {
+    fun appendScroll_indexOutOfBounds_loadDelay0() = appendScroll_indexOutOfBounds(0)
+
+    @Test
+    fun appendScroll_indexOutOfBounds_loadDelay10000() = appendScroll_indexOutOfBounds(10000)
+
+    private fun appendScroll_indexOutOfBounds(loadDelay: Long) {
         val dataFlow = flowOf(List(15) { it })
-        val pager = createPager(dataFlow).cachedIn(testScope.backgroundScope)
+        val pager = createPager(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 // index out of bounds
@@ -1462,9 +1809,14 @@
     }
 
     @Test
-    fun appendScroll_accessPageBoundary() {
+    fun appendScroll_accessPageBoundary_loadDelay0() = appendScroll_accessPageBoundary(0)
+
+    @Test
+    fun appendScroll_accessPageBoundary_loadDelay10000() = appendScroll_accessPageBoundary(10000)
+
+    private fun appendScroll_accessPageBoundary(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPager(dataFlow).cachedIn(testScope.backgroundScope)
+        val pager = createPager(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 // after initial Load and prefetch, max loaded index is 7
@@ -1481,9 +1833,14 @@
     }
 
     @Test
-    fun appendScroll_withoutPrefetch() {
+    fun appendScroll_withoutPrefetch_loadDelay0() = appendScroll_withoutPrefetch(0)
+
+    @Test
+    fun appendScroll_withoutPrefetch_loadDelay10000() = appendScroll_withoutPrefetch(10000)
+
+    private fun appendScroll_withoutPrefetch(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerNoPrefetch(dataFlow)
+        val pager = createPagerNoPrefetch(dataFlow, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 scrollTo(10)
@@ -1497,9 +1854,15 @@
     }
 
     @Test
-    fun appendScroll_withoutPlaceholders() {
+    fun appendScroll_withoutPlaceholders_loadDelay0() = appendScroll_withoutPlaceholders(0)
+
+    @Test
+    fun appendScroll_withoutPlaceholders_loadDelay10000() = appendScroll_withoutPlaceholders(10000)
+
+    private fun appendScroll_withoutPlaceholders(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerNoPlaceholders(dataFlow).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPlaceholders(dataFlow, loadDelay)
+            .cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 // scroll to max loaded index
@@ -1515,9 +1878,17 @@
     }
 
     @Test
-    fun appendScroll_withoutPlaceholders_indexOutOfBounds() {
+    fun appendScroll_withoutPlaceholders_indexOutOfBounds_loadDelay0() =
+        appendScroll_withoutPlaceholders_indexOutOfBounds(0)
+
+    @Test
+    fun appendScroll_withoutPlaceholders_indexOutOfBounds_loadDelay10000() =
+        appendScroll_withoutPlaceholders_indexOutOfBounds(10000)
+
+    private fun appendScroll_withoutPlaceholders_indexOutOfBounds(loadDelay: Long) {
         val dataFlow = flowOf(List(20) { it })
-        val pager = createPagerNoPlaceholders(dataFlow).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPlaceholders(dataFlow, loadDelay)
+            .cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 // 12 is larger than differ.size = 8 after initial refresh
@@ -1535,9 +1906,17 @@
     }
 
     @Test
-    fun appendToScroll_withoutPlaceholders_indexOutOfBoundsIsCapped() {
+    fun appendToScroll_withoutPlaceholders_indexOutOfBoundsIsCapped_loadDelay0() =
+        appendToScroll_withoutPlaceholders_indexOutOfBoundsIsCapped(0)
+
+    @Test
+    fun appendToScroll_withoutPlaceholders_indexOutOfBoundsIsCapped_loadDelay10000() =
+        appendToScroll_withoutPlaceholders_indexOutOfBoundsIsCapped(10000)
+
+    private fun appendToScroll_withoutPlaceholders_indexOutOfBoundsIsCapped(loadDelay: Long) {
         val dataFlow = flowOf(List(20) { it })
-        val pager = createPagerNoPlaceholders(dataFlow).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPlaceholders(dataFlow, loadDelay)
+            .cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 scrollTo(50)
@@ -1553,9 +1932,17 @@
     }
 
     @Test
-    fun consecutiveAppendScroll_withoutPlaceholders() {
+    fun consecutiveAppendScroll_withoutPlaceholders_loadDelay0() =
+        consecutiveAppendScroll_withoutPlaceholders(0)
+
+    @Test
+    fun consecutiveAppendScroll_withoutPlaceholders_loadDelay10000() =
+        consecutiveAppendScroll_withoutPlaceholders(10000)
+
+    private fun consecutiveAppendScroll_withoutPlaceholders(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerNoPlaceholders(dataFlow).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPlaceholders(dataFlow, loadDelay)
+            .cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 scrollTo(12)
@@ -1574,9 +1961,17 @@
     }
 
     @Test
-    fun consecutiveAppendScroll_withoutPlaceholders_multiSnapshot() {
+    fun consecutiveAppendScroll_withoutPlaceholders_multiSnapshot_loadDelay0() =
+        consecutiveAppendScroll_withoutPlaceholders_multiSnapshot(0)
+
+    @Test
+    fun consecutiveAppendScroll_withoutPlaceholders_multiSnapshot_loadDelay10000() =
+        consecutiveAppendScroll_withoutPlaceholders_multiSnapshot(10000)
+
+    private fun consecutiveAppendScroll_withoutPlaceholders_multiSnapshot(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerNoPlaceholders(dataFlow).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPlaceholders(dataFlow, loadDelay)
+            .cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 scrollTo(12)
@@ -1605,9 +2000,15 @@
     }
 
     @Test
-    fun scrollTo_indexAccountsForSeparators() {
+    fun scrollTo_indexAccountsForSeparators_loadDelay0() = scrollTo_indexAccountsForSeparators(0)
+
+    @Test
+    fun scrollTo_indexAccountsForSeparators_loadDelay10000() =
+        scrollTo_indexAccountsForSeparators(10000)
+
+    private fun scrollTo_indexAccountsForSeparators(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPager(dataFlow)
+        val pager = createPager(dataFlow, loadDelay)
         val pagerWithSeparator = pager.map { pagingData ->
             pagingData.insertSeparators { before: Int?, _ ->
                 if (before == 6) "sep" else null
@@ -1639,9 +2040,14 @@
     }
 
     @Test
-    fun prependFling() {
+    fun prependFling_loadDelay0() = prependFling(0)
+
+    @Test
+    fun prependFling_loadDelay10000() = prependFling(10000)
+
+    private fun prependFling(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPager(dataFlow, 50)
+        val pager = createPager(dataFlow, loadDelay, 50)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 flingTo(42)
@@ -1659,9 +2065,14 @@
     }
 
     @Test
-    fun prependFling_withDrops() {
+    fun prependFling_withDrops_loadDelay0() = prependFling_withDrops(0)
+
+    @Test
+    fun prependFling_withDrops_loadDelay10000() = prependFling_withDrops(10000)
+
+    private fun prependFling_withDrops(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerWithDrops(dataFlow, 50)
+        val pager = createPagerWithDrops(dataFlow, loadDelay, 50)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 flingTo(42)
@@ -1674,9 +2085,14 @@
     }
 
     @Test
-    fun prependFling_withSeparators() {
+    fun prependFling_withSeparators_loadDelay0() = prependFling_withSeparators(0)
+
+    @Test
+    fun prependFling_withSeparators_loadDelay10000() = prependFling_withSeparators(10000)
+
+    private fun prependFling_withSeparators(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPager(dataFlow, 50).map { pagingData ->
+        val pager = createPager(dataFlow, loadDelay, 50).map { pagingData ->
             pagingData.insertSeparators { before: Int?, _ ->
                 if (before == 42 || before == 49) "sep" else null
             }
@@ -1699,9 +2115,14 @@
     }
 
     @Test
-    fun consecutivePrependFling() {
+    fun consecutivePrependFling_loadDelay0() = consecutivePrependFling(0)
+
+    @Test
+    fun consecutivePrependFling_loadDelay10000() = consecutivePrependFling(10000)
+
+    private fun consecutivePrependFling(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPager(dataFlow, 50)
+        val pager = createPager(dataFlow, loadDelay, 50)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 flingTo(42)
@@ -1721,9 +2142,16 @@
     }
 
     @Test
-    fun consecutivePrependFling_multiSnapshots() {
+    fun consecutivePrependFling_multiSnapshots_loadDelay0() =
+        consecutivePrependFling_multiSnapshots(0)
+
+    @Test
+    fun consecutivePrependFling_multiSnapshots_loadDelay10000() =
+        consecutivePrependFling_multiSnapshots(10000)
+
+    private fun consecutivePrependFling_multiSnapshots(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPager(dataFlow, 50)
+        val pager = createPager(dataFlow, loadDelay, 50)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 flingTo(42)
@@ -1750,10 +2178,16 @@
             )
         }
     }
+
     @Test
-    fun prependFling_jump() {
+    fun prependFling_jump_loadDelay0() = prependFling_jump(0)
+
+    @Test
+    fun prependFling_jump_loadDelay10000() = prependFling_jump(10000)
+
+    private fun prependFling_jump(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerWithJump(dataFlow, 50)
+        val pager = createPagerWithJump(dataFlow, loadDelay, 50)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 flingTo(30)
@@ -1768,9 +2202,14 @@
     }
 
     @Test
-    fun prependFling_scrollThenJump() {
+    fun prependFling_scrollThenJump_loadDelay0() = prependFling_scrollThenJump(0)
+
+    @Test
+    fun prependFling_scrollThenJump_loadDelay10000() = prependFling_scrollThenJump(10000)
+
+    private fun prependFling_scrollThenJump(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerWithJump(dataFlow, 50)
+        val pager = createPagerWithJump(dataFlow, loadDelay, 50)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 scrollTo(43)
@@ -1786,9 +2225,14 @@
     }
 
     @Test
-    fun prependFling_jumpThenFling() {
+    fun prependFling_jumpThenFling_loadDelay0() = prependFling_jumpThenFling(0)
+
+    @Test
+    fun prependFling_jumpThenFling_loadDelay10000() = prependFling_jumpThenFling(10000)
+
+    private fun prependFling_jumpThenFling(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerWithJump(dataFlow, 50)
+        val pager = createPagerWithJump(dataFlow, loadDelay, 50)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 flingTo(30)
@@ -1806,9 +2250,14 @@
     }
 
     @Test
-    fun prependFling_indexOutOfBounds() {
+    fun prependFling_indexOutOfBounds_loadDelay0() = prependFling_indexOutOfBounds(0)
+
+    @Test
+    fun prependFling_indexOutOfBounds_loadDelay10000() = prependFling_indexOutOfBounds(10000)
+
+    private fun prependFling_indexOutOfBounds(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPager(dataFlow, 10)
+        val pager = createPager(dataFlow, loadDelay, 10)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 flingTo(-3)
@@ -1823,9 +2272,14 @@
     }
 
     @Test
-    fun prependFling_accessPageBoundary() {
+    fun prependFling_accessPageBoundary_loadDelay0() = prependFling_accessPageBoundary(0)
+
+    @Test
+    fun prependFling_accessPageBoundary_loadDelay10000() = prependFling_accessPageBoundary(10000)
+
+    private fun prependFling_accessPageBoundary(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPager(dataFlow, 50)
+        val pager = createPager(dataFlow, loadDelay, 50)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 // page boundary
@@ -1843,9 +2297,15 @@
     }
 
     @Test
-    fun prependFling_withoutPlaceholders() {
+    fun prependFling_withoutPlaceholders_loadDelay0() = prependFling_withoutPlaceholders(0)
+
+    @Test
+    fun prependFling_withoutPlaceholders_loadDelay10000() = prependFling_withoutPlaceholders(10000)
+
+    private fun prependFling_withoutPlaceholders(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerNoPlaceholders(dataFlow, 50).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPlaceholders(dataFlow, loadDelay, 50)
+            .cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 // Without placeholders, first loaded page always starts at index[0]
@@ -1861,9 +2321,16 @@
     }
 
     @Test
-    fun prependFling_withoutPlaceholders_indexOutOfBounds() {
+    fun prependFling_withoutPlaceholders_indexOutOfBounds_loadDelay0() =
+        prependFling_withoutPlaceholders_indexOutOfBounds(0)
+
+    @Test
+    fun prependFling_withoutPlaceholders_indexOutOfBounds_loadDelay10000() =
+        prependFling_withoutPlaceholders_indexOutOfBounds(10000)
+
+    private fun prependFling_withoutPlaceholders_indexOutOfBounds(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerNoPlaceholders(dataFlow, 50)
+        val pager = createPagerNoPlaceholders(dataFlow, loadDelay, 50)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 flingTo(-8)
@@ -1883,9 +2350,17 @@
     }
 
     @Test
-    fun prependFling_withoutPlaceholders_indexOutOfBoundsIsCapped() {
+    fun prependFling_withoutPlaceholders_indexOutOfBoundsIsCapped_loadDelay0() =
+        prependFling_withoutPlaceholders_indexOutOfBoundsIsCapped(0)
+
+    @Test
+    fun prependFling_withoutPlaceholders_indexOutOfBoundsIsCapped_loadDelay10000() =
+        prependFling_withoutPlaceholders_indexOutOfBoundsIsCapped(10000)
+
+    private fun prependFling_withoutPlaceholders_indexOutOfBoundsIsCapped(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerNoPlaceholders(dataFlow, 5).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPlaceholders(dataFlow, loadDelay, 5)
+            .cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 flingTo(-20)
@@ -1901,9 +2376,17 @@
     }
 
     @Test
-    fun consecutivePrependFling_withoutPlaceholders() {
+    fun consecutivePrependFling_withoutPlaceholders_loadDelay0() =
+        consecutivePrependFling_withoutPlaceholders(0)
+
+    @Test
+    fun consecutivePrependFling_withoutPlaceholders_loadDelay10000() =
+        consecutivePrependFling_withoutPlaceholders(10000)
+
+    private fun consecutivePrependFling_withoutPlaceholders(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerNoPlaceholders(dataFlow, 50).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPlaceholders(dataFlow, loadDelay, 50)
+            .cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 // Without placeholders, first loaded page always starts at index[0]
@@ -1927,9 +2410,17 @@
     }
 
     @Test
-    fun consecutivePrependFling_withoutPlaceholders_multiSnapshot() {
+    fun consecutivePrependFling_withoutPlaceholders_multiSnapshot_loadDelay0() =
+        consecutivePrependFling_withoutPlaceholders_multiSnapshot(0)
+
+    @Test
+    fun consecutivePrependFling_withoutPlaceholders_multiSnapshot_loadDelay10000() =
+        consecutivePrependFling_withoutPlaceholders_multiSnapshot(10000)
+
+    private fun consecutivePrependFling_withoutPlaceholders_multiSnapshot(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerNoPlaceholders(dataFlow, 50).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPlaceholders(dataFlow, loadDelay, 50)
+            .cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 // Without placeholders, first loaded page always starts at index[0]
@@ -1959,7 +2450,14 @@
     }
 
     @Test
-    fun prependFling_withoutPlaceholders_indexPrecision() {
+    fun prependFling_withoutPlaceholders_indexPrecision_loadDelay0() =
+        prependFling_withoutPlaceholders_indexPrecision(0)
+
+    @Test
+    fun prependFling_withoutPlaceholders_indexPrecision_loadDelay10000() =
+        prependFling_withoutPlaceholders_indexPrecision(10000)
+
+    private fun prependFling_withoutPlaceholders_indexPrecision(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
         // load sizes and prefetch set to 1 to test precision of flingTo indexing
         val pager = Pager(
@@ -1970,7 +2468,7 @@
                 prefetchDistance = 1
             ),
             initialKey = 50,
-            pagingSourceFactory = createFactory(dataFlow),
+            pagingSourceFactory = createFactory(dataFlow, loadDelay),
         )
         testScope.runTest {
             val snapshot = pager.flow.asSnapshot {
@@ -1988,9 +2486,14 @@
     }
 
     @Test
-    fun appendFling() {
+    fun appendFling_loadDelay0() = appendFling(0)
+
+    @Test
+    fun appendFling_loadDelay10000() = appendFling(10000)
+
+    private fun appendFling(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPager(dataFlow)
+        val pager = createPager(dataFlow, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 flingTo(12)
@@ -2006,9 +2509,14 @@
     }
 
     @Test
-    fun appendFling_withDrops() {
+    fun appendFling_withDrops_loadDelay0() = appendFling_withDrops(0)
+
+    @Test
+    fun appendFling_withDrops_loadDelay10000() = appendFling_withDrops(10000)
+
+    private fun appendFling_withDrops(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerWithDrops(dataFlow)
+        val pager = createPagerWithDrops(dataFlow, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 flingTo(12)
@@ -2021,9 +2529,14 @@
     }
 
     @Test
-    fun appendFling_withSeparators() {
+    fun appendFling_withSeparators_loadDelay0() = appendFling_withSeparators(0)
+
+    @Test
+    fun appendFling_withSeparators_loadDelay10000() = appendFling_withSeparators(10000)
+
+    private fun appendFling_withSeparators(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPager(dataFlow).map { pagingData ->
+        val pager = createPager(dataFlow, loadDelay).map { pagingData ->
             pagingData.insertSeparators { before: Int?, _ ->
                 if (before == 0 || before == 14) "sep" else null
             }
@@ -2043,9 +2556,14 @@
     }
 
     @Test
-    fun consecutiveAppendFling() {
+    fun consecutiveAppendFling_loadDelay0() = consecutiveAppendFling(0)
+
+    @Test
+    fun consecutiveAppendFling_loadDelay10000() = consecutiveAppendFling(10000)
+
+    private fun consecutiveAppendFling(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPager(dataFlow)
+        val pager = createPager(dataFlow, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 flingTo(12)
@@ -2063,9 +2581,16 @@
     }
 
     @Test
-    fun consecutiveAppendFling_multiSnapshots() {
+    fun consecutiveAppendFling_multiSnapshots_loadDelay0() =
+        consecutiveAppendFling_multiSnapshots(0)
+
+    @Test
+    fun consecutiveAppendFling_multiSnapshots_loadDelay10000() =
+        consecutiveAppendFling_multiSnapshots(10000)
+
+    private fun consecutiveAppendFling_multiSnapshots(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPager(dataFlow)
+        val pager = createPager(dataFlow, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 flingTo(12)
@@ -2092,9 +2617,14 @@
     }
 
     @Test
-    fun appendFling_jump() {
+    fun appendFling_jump_loadDelay0() = appendFling_jump(0)
+
+    @Test
+    fun appendFling_jump_loadDelay10000() = appendFling_jump(10000)
+
+    private fun appendFling_jump(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerWithJump(dataFlow)
+        val pager = createPagerWithJump(dataFlow, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 flingTo(30)
@@ -2109,9 +2639,14 @@
     }
 
     @Test
-    fun appendFling_scrollThenJump() {
+    fun appendFling_scrollThenJump_loadDelay0() = appendFling_scrollThenJump(0)
+
+    @Test
+    fun appendFling_scrollThenJump_loadDelay10000() = appendFling_scrollThenJump(10000)
+
+    private fun appendFling_scrollThenJump(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerWithJump(dataFlow)
+        val pager = createPagerWithJump(dataFlow, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 scrollTo(30)
@@ -2127,9 +2662,14 @@
     }
 
     @Test
-    fun appendFling_jumpThenFling() {
+    fun appendFling_jumpThenFling_loadDelay0() = appendFling_jumpThenFling(0)
+
+    @Test
+    fun appendFling_jumpThenFling_loadDelay10000() = appendFling_jumpThenFling(10000)
+
+    private fun appendFling_jumpThenFling(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerWithJump(dataFlow)
+        val pager = createPagerWithJump(dataFlow, loadDelay)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 flingTo(30)
@@ -2147,9 +2687,14 @@
     }
 
     @Test
-    fun appendFling_indexOutOfBounds() {
+    fun appendFling_indexOutOfBounds_loadDelay0() = appendFling_indexOutOfBounds(0)
+
+    @Test
+    fun appendFling_indexOutOfBounds_loadDelay10000() = appendFling_indexOutOfBounds(10000)
+
+    private fun appendFling_indexOutOfBounds(loadDelay: Long) {
         val dataFlow = flowOf(List(15) { it })
-        val pager = createPager(dataFlow).cachedIn(testScope.backgroundScope)
+        val pager = createPager(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 // index out of bounds
@@ -2165,9 +2710,14 @@
     }
 
     @Test
-    fun appendFling_accessPageBoundary() {
+    fun appendFling_accessPageBoundary_loadDelay0() = appendFling_accessPageBoundary(0)
+
+    @Test
+    fun appendFling_accessPageBoundary_loadDelay10000() = appendFling_accessPageBoundary(10000)
+
+    private fun appendFling_accessPageBoundary(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPager(dataFlow).cachedIn(testScope.backgroundScope)
+        val pager = createPager(dataFlow, loadDelay).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 // after initial Load and prefetch, max loaded index is 7
@@ -2184,9 +2734,15 @@
     }
 
     @Test
-    fun appendFling_withoutPlaceholders() {
+    fun appendFling_withoutPlaceholders_loadDelay0() = appendFling_withoutPlaceholders(0)
+
+    @Test
+    fun appendFling_withoutPlaceholders_loadDelay10000() = appendFling_withoutPlaceholders(10000)
+
+    private fun appendFling_withoutPlaceholders(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerNoPlaceholders(dataFlow).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPlaceholders(dataFlow, loadDelay)
+            .cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 // scroll to max loaded index
@@ -2202,9 +2758,17 @@
     }
 
     @Test
-    fun appendFling_withoutPlaceholders_indexOutOfBounds() {
+    fun appendFling_withoutPlaceholders_indexOutOfBounds_loadDelay0() =
+        appendFling_withoutPlaceholders_indexOutOfBounds(0)
+
+    @Test
+    fun appendFling_withoutPlaceholders_indexOutOfBounds_loadDelay10000() =
+        appendFling_withoutPlaceholders_indexOutOfBounds(10000)
+
+    private fun appendFling_withoutPlaceholders_indexOutOfBounds(loadDelay: Long) {
         val dataFlow = flowOf(List(20) { it })
-        val pager = createPagerNoPlaceholders(dataFlow).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPlaceholders(dataFlow, loadDelay)
+            .cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 // 12 is larger than differ.size = 8 after initial refresh
@@ -2222,9 +2786,17 @@
     }
 
     @Test
-    fun appendFling_withoutPlaceholders_indexOutOfBoundsIsCapped() {
+    fun appendFling_withoutPlaceholders_indexOutOfBoundsIsCapped_loadDelay0() =
+        appendFling_withoutPlaceholders_indexOutOfBoundsIsCapped(0)
+
+    @Test
+    fun appendFling_withoutPlaceholders_indexOutOfBoundsIsCapped_loadDelay10000() =
+        appendFling_withoutPlaceholders_indexOutOfBoundsIsCapped(10000)
+
+    private fun appendFling_withoutPlaceholders_indexOutOfBoundsIsCapped(loadDelay: Long) {
         val dataFlow = flowOf(List(20) { it })
-        val pager = createPagerNoPlaceholders(dataFlow).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPlaceholders(dataFlow, loadDelay)
+            .cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 flingTo(50)
@@ -2240,9 +2812,17 @@
     }
 
     @Test
-    fun consecutiveAppendFling_withoutPlaceholders() {
+    fun consecutiveAppendFling_withoutPlaceholders_loadDelay0() =
+        consecutiveAppendFling_withoutPlaceholders(0)
+
+    @Test
+    fun consecutiveAppendFling_withoutPlaceholders_loadDelay10000() =
+        consecutiveAppendFling_withoutPlaceholders(10000)
+
+    private fun consecutiveAppendFling_withoutPlaceholders(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerNoPlaceholders(dataFlow).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPlaceholders(dataFlow, loadDelay)
+            .cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 flingTo(12)
@@ -2261,9 +2841,17 @@
     }
 
     @Test
-    fun consecutiveAppendFling_withoutPlaceholders_multiSnapshot() {
+    fun consecutiveAppendFling_withoutPlaceholders_multiSnapshot_loadDelay0() =
+        consecutiveAppendFling_withoutPlaceholders_multiSnapshot(0)
+
+    @Test
+    fun consecutiveAppendFling_withoutPlaceholders_multiSnapshot_loadDelay10000() =
+        consecutiveAppendFling_withoutPlaceholders_multiSnapshot(10000)
+
+    private fun consecutiveAppendFling_withoutPlaceholders_multiSnapshot(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
-        val pager = createPagerNoPlaceholders(dataFlow).cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPlaceholders(dataFlow, loadDelay)
+            .cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot {
                 flingTo(12)
@@ -2292,7 +2880,14 @@
     }
 
     @Test
-    fun appendFling_withoutPlaceholders_indexPrecision() {
+    fun appendFling_withoutPlaceholders_indexPrecision_loadDelay0() =
+        appendFling_withoutPlaceholders_indexPrecision(0)
+
+    @Test
+    fun appendFling_withoutPlaceholders_indexPrecision_loadDelay10000() =
+        appendFling_withoutPlaceholders_indexPrecision(10000)
+
+    private fun appendFling_withoutPlaceholders_indexPrecision(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
         // load sizes and prefetch set to 1 to test precision of flingTo indexing
         val pager = Pager(
@@ -2302,7 +2897,7 @@
                 enablePlaceholders = false,
                 prefetchDistance = 1
             ),
-            pagingSourceFactory = createFactory(dataFlow),
+            pagingSourceFactory = createFactory(dataFlow, loadDelay),
         )
         testScope.runTest {
             val snapshot = pager.flow.asSnapshot {
@@ -2320,7 +2915,13 @@
     }
 
     @Test
-    fun flingTo_indexAccountsForSeparators() {
+    fun flingTo_indexAccountsForSeparators_loadDelay0() = flingTo_indexAccountsForSeparators(0)
+
+    @Test
+    fun flingTo_indexAccountsForSeparators_loadDelay10000() =
+        flingTo_indexAccountsForSeparators(10000)
+
+    private fun flingTo_indexAccountsForSeparators(loadDelay: Long) {
         val dataFlow = flowOf(List(100) { it })
         val pager = createPager(
             dataFlow,
@@ -2329,6 +2930,7 @@
                 initialLoadSize = 1,
                 prefetchDistance = 1
             ),
+            loadDelay,
             50
         )
         val pagerWithSeparator = pager.map { pagingData ->
@@ -2360,9 +2962,14 @@
     }
 
     @Test
-    fun errorHandler_throw() {
+    fun errorHandler_throw_loadDelay0() = errorHandler_throw(0)
+
+    @Test
+    fun errorHandler_throw_loadDelay10000() = errorHandler_throw(10000)
+
+    private fun errorHandler_throw(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val factory = createFactory(dataFlow)
+        val factory = createFactory(dataFlow, loadDelay)
         val pagingSources = mutableListOf<TestPagingSource>()
         val pager = Pager(
             config = PagingConfig(pageSize = 3, initialLoadSize = 5),
@@ -2383,9 +2990,14 @@
     }
 
     @Test
-    fun errorHandler_retry() {
+    fun errorHandler_retry_loadDelay0() = errorHandler_retry(0)
+
+    @Test
+    fun errorHandler_retry_loadDelay10000() = errorHandler_retry(10000)
+
+    private fun errorHandler_retry(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val factory = createFactory(dataFlow)
+        val factory = createFactory(dataFlow, loadDelay)
         val pagingSources = mutableListOf<TestPagingSource>()
         val pager = Pager(
             config = PagingConfig(pageSize = 3, initialLoadSize = 5),
@@ -2422,9 +3034,14 @@
     }
 
     @Test
-    fun errorHandler_retryFails() {
+    fun errorHandler_retryFails_loadDelay0() = errorHandler_retryFails(0)
+
+    @Test
+    fun errorHandler_retryFails_loadDelay10000() = errorHandler_retryFails(10000)
+
+    private fun errorHandler_retryFails(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val factory = createFactory(dataFlow)
+        val factory = createFactory(dataFlow, loadDelay)
         val pagingSources = mutableListOf<TestPagingSource>()
         val pager = Pager(
             config = PagingConfig(pageSize = 3, initialLoadSize = 5),
@@ -2463,9 +3080,14 @@
     }
 
     @Test
-    fun errorHandler_returnSnapshot() {
+    fun errorHandler_returnSnapshot_loadDelay0() = errorHandler_returnSnapshot(0)
+
+    @Test
+    fun errorHandler_returnSnapshot_loadDelay10000() = errorHandler_returnSnapshot(10000)
+
+    private fun errorHandler_returnSnapshot(loadDelay: Long) {
         val dataFlow = flowOf(List(30) { it })
-        val factory = createFactory(dataFlow)
+        val factory = createFactory(dataFlow, loadDelay)
         val pagingSources = mutableListOf<TestPagingSource>()
         val pager = Pager(
             config = PagingConfig(pageSize = 3, initialLoadSize = 5),
@@ -2486,21 +3108,26 @@
         }
     }
 
-    private fun createPager(dataFlow: Flow<List<Int>>, initialKey: Int = 0) =
+    private fun createPager(dataFlow: Flow<List<Int>>, loadDelay: Long, initialKey: Int = 0) =
         createPager(
             dataFlow,
             PagingConfig(pageSize = 3, initialLoadSize = 5),
+            loadDelay,
             initialKey
         )
 
-    private fun createPager(data: List<Int>, initialKey: Int = 0) =
+    private fun createPager(data: List<Int>, loadDelay: Long, initialKey: Int = 0) =
         Pager(
             PagingConfig(pageSize = 3, initialLoadSize = 5),
             initialKey,
-            createSingleGenFactory(data),
+            createSingleGenFactory(data, loadDelay),
         ).flow
 
-    private fun createPagerNoPlaceholders(dataFlow: Flow<List<Int>>, initialKey: Int = 0) =
+    private fun createPagerNoPlaceholders(
+        dataFlow: Flow<List<Int>>,
+        loadDelay: Long,
+        initialKey: Int = 0
+    ) =
         createPager(
             dataFlow,
             PagingConfig(
@@ -2509,37 +3136,54 @@
                 enablePlaceholders = false,
                 prefetchDistance = 3
             ),
+            loadDelay,
             initialKey)
 
-    private fun createPagerNoPrefetch(dataFlow: Flow<List<Int>>, initialKey: Int = 0) =
+    private fun createPagerNoPrefetch(
+        dataFlow: Flow<List<Int>>,
+        loadDelay: Long,
+        initialKey: Int = 0
+    ) =
         createPager(
             dataFlow,
             PagingConfig(pageSize = 3, initialLoadSize = 5, prefetchDistance = 0),
+            loadDelay,
             initialKey
         )
 
-    private fun createPagerWithJump(dataFlow: Flow<List<Int>>, initialKey: Int = 0) =
+    private fun createPagerWithJump(
+        dataFlow: Flow<List<Int>>,
+        loadDelay: Long,
+        initialKey: Int = 0
+    ) =
         createPager(
             dataFlow,
             PagingConfig(pageSize = 3, initialLoadSize = 5, jumpThreshold = 5),
+            loadDelay,
             initialKey
         )
 
-    private fun createPagerWithDrops(dataFlow: Flow<List<Int>>, initialKey: Int = 0) =
+    private fun createPagerWithDrops(
+        dataFlow: Flow<List<Int>>,
+        loadDelay: Long,
+        initialKey: Int = 0
+    ) =
         createPager(
             dataFlow,
             PagingConfig(pageSize = 3, initialLoadSize = 5, maxSize = 9),
+            loadDelay,
             initialKey
         )
 
     private fun createPager(
         dataFlow: Flow<List<Int>>,
         config: PagingConfig,
+        loadDelay: Long,
         initialKey: Int = 0,
     ) = Pager(
             config = config,
             initialKey = initialKey,
-            pagingSourceFactory = createFactory(dataFlow),
+            pagingSourceFactory = createFactory(dataFlow, loadDelay),
         ).flow
 }
 
diff --git a/paging/paging-testing/src/androidUnitTest/kotlin/androidx/paging/testing/StaticListPagingSourceFactoryTest.kt b/paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/StaticListPagingSourceFactoryTest.kt
similarity index 97%
rename from paging/paging-testing/src/androidUnitTest/kotlin/androidx/paging/testing/StaticListPagingSourceFactoryTest.kt
rename to paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/StaticListPagingSourceFactoryTest.kt
index b4cc8d1..0419b4f 100644
--- a/paging/paging-testing/src/androidUnitTest/kotlin/androidx/paging/testing/StaticListPagingSourceFactoryTest.kt
+++ b/paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/StaticListPagingSourceFactoryTest.kt
@@ -16,10 +16,11 @@
 
 package androidx.paging.testing
 
+import androidx.kruth.assertThat
 import androidx.paging.PagingConfig
 import androidx.paging.PagingSource.LoadResult.Page
 import androidx.paging.PagingSourceFactory
-import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.delay
@@ -31,12 +32,8 @@
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.advanceUntilIdle
 import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 
 @OptIn(ExperimentalCoroutinesApi::class)
-@RunWith(JUnit4::class)
 class StaticListPagingSourceFactoryTest {
 
     private val testScope = TestScope(UnconfinedTestDispatcher())
diff --git a/paging/paging-testing/src/androidUnitTest/kotlin/androidx/paging/testing/StaticListPagingSourceTest.kt b/paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/StaticListPagingSourceTest.kt
similarity index 97%
rename from paging/paging-testing/src/androidUnitTest/kotlin/androidx/paging/testing/StaticListPagingSourceTest.kt
rename to paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/StaticListPagingSourceTest.kt
index dd4197f..ae8ce3b 100644
--- a/paging/paging-testing/src/androidUnitTest/kotlin/androidx/paging/testing/StaticListPagingSourceTest.kt
+++ b/paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/StaticListPagingSourceTest.kt
@@ -16,16 +16,13 @@
 
 package androidx.paging.testing
 
+import androidx.kruth.assertThat
 import androidx.paging.PagingConfig
 import androidx.paging.PagingSource
 import androidx.paging.PagingSource.LoadResult
-import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
 import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 
-@RunWith(JUnit4::class)
 class StaticListPagingSourceTest {
 
     private val DATA = List(100) { it }
diff --git a/paging/paging-testing/src/androidUnitTest/kotlin/androidx/paging/testing/TestPagerTest.kt b/paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/TestPagerTest.kt
similarity index 98%
rename from paging/paging-testing/src/androidUnitTest/kotlin/androidx/paging/testing/TestPagerTest.kt
rename to paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/TestPagerTest.kt
index 477450e..b534856 100644
--- a/paging/paging-testing/src/androidUnitTest/kotlin/androidx/paging/testing/TestPagerTest.kt
+++ b/paging/paging-testing/src/commonTest/kotlin/androidx/paging/testing/TestPagerTest.kt
@@ -16,24 +16,21 @@
 
 package androidx.paging.testing
 
+import androidx.kruth.assertThat
 import androidx.paging.PagingConfig
 import androidx.paging.PagingSource.LoadResult
 import androidx.paging.PagingState
 import androidx.paging.TestPagingSource
-import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
 import kotlin.test.assertFailsWith
+import kotlin.test.assertTrue
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.advanceUntilIdle
 import kotlinx.coroutines.test.runTest
-import org.junit.Assert.assertTrue
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 
 @OptIn(ExperimentalCoroutinesApi::class)
-@RunWith(JUnit4::class)
 class TestPagerTest {
 
     @Test
@@ -101,7 +98,7 @@
             // simulate a PagingSource that returns LoadResult.Invalid when it's invalidated
             source.nextLoadResult = LoadResult.Invalid()
 
-            assertThat(pager.refresh()).isInstanceOf(LoadResult.Invalid::class.java)
+            assertThat(pager.refresh()).isInstanceOf<LoadResult.Invalid<Int, Int>>()
         }
     }
 
@@ -361,7 +358,7 @@
             source.nextLoadResult = LoadResult.Invalid()
             append()
         }
-        assertThat(result).isInstanceOf(LoadResult.Invalid::class.java)
+        assertThat(result).isInstanceOf<LoadResult.Invalid<Int, Int>>()
     }
 
     @Test
@@ -377,7 +374,7 @@
             source.nextLoadResult = LoadResult.Invalid()
             prepend()
         }
-        assertThat(result).isInstanceOf(LoadResult.Invalid::class.java)
+        assertThat(result).isInstanceOf<LoadResult.Invalid<Int, Int>>()
     }
 
     @Test
@@ -642,7 +639,7 @@
                 append()
                 getPagingState(-1)
             }
-        }.localizedMessage
+        }.message
         assertThat(msg).isEqualTo(
             "anchorPosition -1 is out of bounds between [0..${ITEM_COUNT - 1}]. Please " +
                 "provide a valid anchorPosition."
@@ -650,7 +647,7 @@
 
         val msg2 = assertFailsWith<IllegalStateException> {
             pager.getPagingState(ITEM_COUNT)
-        }.localizedMessage
+        }.message
         assertThat(msg2).isEqualTo(
             "anchorPosition $ITEM_COUNT is out of bounds between [0..${ITEM_COUNT - 1}]. " +
                 "Please provide a valid anchorPosition."
@@ -675,7 +672,7 @@
                 append()
                 getPagingState(-1)
             }
-        }.localizedMessage
+        }.message
         assertThat(msg).isEqualTo(
             "anchorPosition -1 is out of bounds between [0..7]. Please " +
                 "provide a valid anchorPosition."
@@ -684,7 +681,7 @@
         // total loaded items = 8, anchorPos with index 8 should be out of bounds
         val msg2 = assertFailsWith<IllegalStateException> {
             pager.getPagingState(8)
-        }.localizedMessage
+        }.message
         assertThat(msg2).isEqualTo(
             "anchorPosition 8 is out of bounds between [0..7]. Please " +
         "provide a valid anchorPosition."
diff --git a/paging/paging-testing/src/jvmMain/kotlin/androidx/paging/testing/internal/Atomics.jvm.kt b/paging/paging-testing/src/jvmMain/kotlin/androidx/paging/testing/internal/Atomics.jvm.kt
new file mode 100644
index 0000000..b9532be
--- /dev/null
+++ b/paging/paging-testing/src/jvmMain/kotlin/androidx/paging/testing/internal/Atomics.jvm.kt
@@ -0,0 +1,23 @@
+/*
+ * 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("ACTUAL_WITHOUT_EXPECT") // https://youtrack.jetbrains.com/issue/KT-37316
+package androidx.paging.testing.internal
+
+internal actual typealias AtomicInt = java.util.concurrent.atomic.AtomicInteger
+
+internal actual typealias AtomicBoolean = java.util.concurrent.atomic.AtomicBoolean
+
+internal actual typealias AtomicRef<T> = java.util.concurrent.atomic.AtomicReference<T>
diff --git a/paging/paging-testing/src/nativeMain/kotlin/androidx/paging/testing/internal/Atomics.native.kt b/paging/paging-testing/src/nativeMain/kotlin/androidx/paging/testing/internal/Atomics.native.kt
new file mode 100644
index 0000000..bc053e9
--- /dev/null
+++ b/paging/paging-testing/src/nativeMain/kotlin/androidx/paging/testing/internal/Atomics.native.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.
+ */
+
+@file:OptIn(ExperimentalForeignApi::class)
+
+package androidx.paging.testing.internal
+
+import kotlinx.atomicfu.AtomicBoolean as AtomicFuAtomicBoolean
+import kotlinx.atomicfu.AtomicInt as AtomicFuAtomicInt
+import kotlinx.atomicfu.AtomicRef as AtomicFuAtomicRef
+import kotlinx.atomicfu.atomic
+import kotlinx.cinterop.ExperimentalForeignApi
+
+internal actual class AtomicInt actual constructor(initialValue: Int) {
+    private var delegate: AtomicFuAtomicInt = atomic(initialValue)
+    private var property by delegate
+
+    actual fun get(): Int = property
+
+    actual fun set(value: Int) {
+        property = value
+    }
+}
+
+internal actual class AtomicBoolean actual constructor(initialValue: Boolean) {
+    private var delegate: AtomicFuAtomicBoolean = atomic(initialValue)
+    private var property by delegate
+
+    actual fun get(): Boolean = property
+
+    actual fun set(value: Boolean) {
+        property = value
+    }
+
+    actual fun compareAndSet(expect: Boolean, update: Boolean): Boolean {
+        return delegate.compareAndSet(expect, update)
+    }
+}
+
+internal actual class AtomicRef<T> actual constructor(initialValue: T) {
+    private var delegate: AtomicFuAtomicRef<T> = atomic(initialValue)
+    private var property by delegate
+
+    actual fun get(): T = property
+
+    actual fun set(value: T) {
+        property = value
+    }
+
+    actual fun getAndSet(value: T): T {
+        return delegate.getAndSet(value)
+    }
+}
diff --git a/playground-common/androidx-shared.properties b/playground-common/androidx-shared.properties
index faee4a0..8a53805 100644
--- a/playground-common/androidx-shared.properties
+++ b/playground-common/androidx-shared.properties
@@ -35,7 +35,7 @@
 org.gradle.configuration-cache.problems=fail
 
 android.lint.printStackTrace=true
-android.uniquePackageNames=false
+android.uniquePackageNames=true
 android.enableAdditionalTestOutput=true
 android.useAndroidX=true
 android.nonTransitiveRClass=true
diff --git a/privacysandbox/ads/ads-adservices-java/build.gradle b/privacysandbox/ads/ads-adservices-java/build.gradle
index d65ff5c0..b3a4b71 100644
--- a/privacysandbox/ads/ads-adservices-java/build.gradle
+++ b/privacysandbox/ads/ads-adservices-java/build.gradle
@@ -36,8 +36,10 @@
 
     androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.0'
     androidTestImplementation project(path: ':privacysandbox:ads:ads-adservices')
+    androidTestImplementation project(path: ':javascriptengine:javascriptengine')
     androidTestImplementation(libs.junit)
     androidTestImplementation(libs.kotlinTestJunit)
+    androidTestImplementation(libs.multidex)
     androidTestImplementation(libs.testCore)
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testRunner)
@@ -46,9 +48,14 @@
 
     androidTestImplementation(libs.mockitoCore4, excludes.bytebuddy) // DexMaker has it"s own MockMaker
     androidTestImplementation(libs.dexmakerMockitoInline, excludes.bytebuddy) // DexMaker has it"s own MockMaker
+    androidTestImplementation(libs.dexmakerMockitoInlineExtended)
 }
 
 android {
+    defaultConfig {
+        multiDexEnabled = true
+    }
+
     namespace "androidx.privacysandbox.ads.adservices.java"
 }
 
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/AndroidManifest.xml b/privacysandbox/ads/ads-adservices-java/src/androidTest/AndroidManifest.xml
index 90665d4..aa19a69 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/AndroidManifest.xml
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/AndroidManifest.xml
@@ -14,11 +14,13 @@
   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">
     <uses-permission android:name="android.permission.ACCESS_ADSERVICES_TOPICS" />
     <uses-permission android:name="android.permission.ACCESS_ADSERVICES_AD_ID" />
     <uses-permission android:name="android.permission.ACCESS_ADSERVICES_ATTRIBUTION" />
     <uses-permission android:name="android.permission.ACCESS_ADSERVICES_CUSTOM_AUDIENCE" />
+    <uses-sdk tools:overrideLibrary="androidx.javascriptengine" />
     <application>
         <property android:name="android.adservices.AD_SERVICES_CONFIG"
             android:resource="@xml/ad_services_config" />
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/VersionCompatUtil.kt b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/VersionCompatUtil.kt
new file mode 100644
index 0000000..d4ad1a2
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/VersionCompatUtil.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.java
+
+import android.os.Build
+import android.os.ext.SdkExtensions
+import androidx.test.filters.SdkSuppress
+
+@SdkSuppress(minSdkVersion = 30)
+object VersionCompatUtil {
+    fun isTPlusWithMinAdServicesVersion(minVersion: Int): Boolean {
+        return Build.VERSION.SDK_INT >= 33 &&
+            SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES) >= minVersion
+    }
+
+    fun isSWithMinExtServicesVersion(minVersion: Int): Boolean {
+        return (Build.VERSION.SDK_INT == 31 || Build.VERSION.SDK_INT == 32) &&
+            SdkExtensions.getExtensionVersion(Build.VERSION_CODES.S) >= minVersion
+    }
+
+    fun isTestableVersion(minAdServicesVersion: Int, minExtServicesVersion: Int): Boolean {
+        return isTPlusWithMinAdServicesVersion(minAdServicesVersion) ||
+            isSWithMinExtServicesVersion(minExtServicesVersion)
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/adid/AdIdManagerFuturesTest.kt b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/adid/AdIdManagerFuturesTest.kt
index 5f20ba7..35d5109 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/adid/AdIdManagerFuturesTest.kt
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/adid/AdIdManagerFuturesTest.kt
@@ -19,16 +19,18 @@
 import android.content.Context
 import android.os.Looper
 import android.os.OutcomeReceiver
-import android.os.ext.SdkExtensions
-import androidx.annotation.RequiresExtension
 import androidx.privacysandbox.ads.adservices.adid.AdId
+import androidx.privacysandbox.ads.adservices.java.VersionCompatUtil
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.google.common.truth.Truth
 import com.google.common.util.concurrent.ListenableFuture
 import kotlin.test.assertNotEquals
+import org.junit.After
 import org.junit.Assert
 import org.junit.Assume
 import org.junit.Before
@@ -36,6 +38,8 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers
 import org.mockito.Mockito
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.`when`
 import org.mockito.invocation.InvocationOnMock
 
 @SmallTest
@@ -44,25 +48,45 @@
 @SdkSuppress(minSdkVersion = 30)
 class AdIdManagerFuturesTest {
 
+    private var mSession: StaticMockitoSession? = null
+    private val mValidAdExtServicesSdkExtVersion = VersionCompatUtil.isSWithMinExtServicesVersion(9)
+
     @Before
     fun setUp() {
-        mContext = Mockito.spy(ApplicationProvider.getApplicationContext<Context>())
+        mContext = spy(ApplicationProvider.getApplicationContext<Context>())
+
+        if (mValidAdExtServicesSdkExtVersion) {
+            // setup a mockitoSession to return the mocked manager
+            // when the static method .get() is called
+            mSession = ExtendedMockito.mockitoSession()
+                .mockStatic(android.adservices.adid.AdIdManager::class.java)
+                .startMocking()
+        }
+    }
+
+    @After
+    fun tearDown() {
+        mSession?.finishMocking()
     }
 
     @Test
     @SdkSuppress(maxSdkVersion = 33, minSdkVersion = 30)
     fun testAdIdOlderVersions() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
-        Assume.assumeTrue("maxSdkVersion = API 33 ext 3", sdkExtVersion < 4)
+        Assume.assumeFalse("maxSdkVersion = API 33 ext 3 or API 31/32 ext 8",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion=*/ 4,
+                /* minExtServicesVersion=*/ 9))
         Truth.assertThat(AdIdManagerFutures.from(mContext)).isEqualTo(null)
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
     fun testAdIdAsync() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
-        val adIdManager = mockAdIdManager(mContext)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion= */ 4,
+                /* minExtServicesVersion=*/ 9))
+
+        val adIdManager = mockAdIdManager(mContext, mValidAdExtServicesSdkExtVersion)
         setupResponse(adIdManager)
         val managerCompat = AdIdManagerFutures.from(mContext)
 
@@ -76,16 +100,23 @@
         Mockito.verify(adIdManager).getAdId(ArgumentMatchers.any(), ArgumentMatchers.any())
     }
 
-    @SuppressWarnings("NewApi")
     @SdkSuppress(minSdkVersion = 30)
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
     companion object {
         private lateinit var mContext: Context
 
-        private fun mockAdIdManager(spyContext: Context): android.adservices.adid.AdIdManager {
+        private fun mockAdIdManager(
+            spyContext: Context,
+            isExtServices: Boolean
+        ): android.adservices.adid.AdIdManager {
             val adIdManager = Mockito.mock(android.adservices.adid.AdIdManager::class.java)
-            Mockito.`when`(spyContext.getSystemService(
-                android.adservices.adid.AdIdManager::class.java)).thenReturn(adIdManager)
+            // mock the .get() method if using extServices version, otherwise mock getSystemService
+            if (isExtServices) {
+                `when`(android.adservices.adid.AdIdManager.get(ArgumentMatchers.any()))
+                    .thenReturn(adIdManager)
+            } else {
+                `when`(spyContext.getSystemService(
+                    android.adservices.adid.AdIdManager::class.java)).thenReturn(adIdManager)
+            }
             return adIdManager
         }
 
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/adselection/AdSelectionManagerFuturesTest.kt b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/adselection/AdSelectionManagerFuturesTest.kt
index af4ef91..afc5c68 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/adselection/AdSelectionManagerFuturesTest.kt
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/adselection/AdSelectionManagerFuturesTest.kt
@@ -19,20 +19,22 @@
 import android.content.Context
 import android.net.Uri
 import android.os.OutcomeReceiver
-import android.os.ext.SdkExtensions
-import androidx.annotation.RequiresExtension
 import androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig
 import androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome
 import androidx.privacysandbox.ads.adservices.adselection.ReportImpressionRequest
 import androidx.privacysandbox.ads.adservices.common.AdSelectionSignals
 import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
+import androidx.privacysandbox.ads.adservices.java.VersionCompatUtil
 import androidx.privacysandbox.ads.adservices.java.adselection.AdSelectionManagerFutures.Companion.from
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.google.common.truth.Truth
 import com.google.common.util.concurrent.ListenableFuture
+import org.junit.After
 import org.junit.Assert
 import org.junit.Assume
 import org.junit.Before
@@ -46,6 +48,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.invocation.InvocationOnMock
+import org.mockito.quality.Strictness
 
 @SmallTest
 @SuppressWarnings("NewApi")
@@ -53,27 +56,46 @@
 @SdkSuppress(minSdkVersion = 30)
 class AdSelectionManagerFuturesTest {
 
+    private var mSession: StaticMockitoSession? = null
+    private val mValidAdExtServicesSdkExtVersion = VersionCompatUtil.isSWithMinExtServicesVersion(9)
+
     @Before
     fun setUp() {
         mContext = spy(ApplicationProvider.getApplicationContext<Context>())
+
+        if (mValidAdExtServicesSdkExtVersion) {
+            // setup a mockitoSession to return the mocked manager
+            // when the static method .get() is called
+            mSession = ExtendedMockito.mockitoSession()
+                .mockStatic(android.adservices.adselection.AdSelectionManager::class.java)
+                .strictness(Strictness.LENIENT)
+                .startMocking()
+        }
+    }
+
+    @After
+    fun tearDown() {
+        mSession?.finishMocking()
     }
 
     @Test
     @SdkSuppress(maxSdkVersion = 33, minSdkVersion = 30)
     fun testAdSelectionOlderVersions() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
-
-        Assume.assumeTrue("maxSdkVersion = API 33 ext 3", sdkExtVersion < 4)
+        Assume.assumeFalse("maxSdkVersion = API 33 ext 3 or API 31/32 ext 8",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion=*/ 4,
+                /* minExtServicesVersion=*/ 9))
         Truth.assertThat(from(mContext)).isEqualTo(null)
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
     fun testSelectAds() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion= */ 4,
+                /* minExtServicesVersion=*/ 9))
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
-        val adSelectionManager = mockAdSelectionManager(mContext)
+        val adSelectionManager = mockAdSelectionManager(mContext, mValidAdExtServicesSdkExtVersion)
         setupAdSelectionResponse(adSelectionManager)
         val managerCompat = from(mContext)
 
@@ -95,12 +117,13 @@
 
     @Test
     @SuppressWarnings("NewApi")
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
     fun testReportImpression() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion= */ 4,
+                /* minExtServicesVersion=*/ 9))
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
-        val adSelectionManager = mockAdSelectionManager(mContext)
+        val adSelectionManager = mockAdSelectionManager(mContext, mValidAdExtServicesSdkExtVersion)
         setupAdSelectionResponse(adSelectionManager)
         val managerCompat = from(mContext)
         val reportImpressionRequest = ReportImpressionRequest(adSelectionId, adSelectionConfig)
@@ -119,7 +142,6 @@
 
     @SuppressWarnings("NewApi")
     @SdkSuppress(minSdkVersion = 30)
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
     companion object {
         private lateinit var mContext: Context
         private const val adSelectionId = 1234L
@@ -148,13 +170,20 @@
         private val renderUri = Uri.parse("render-uri.com")
 
         private fun mockAdSelectionManager(
-            spyContext: Context
+            spyContext: Context,
+            isExtServices: Boolean
         ): android.adservices.adselection.AdSelectionManager {
             val adSelectionManager =
                 mock(android.adservices.adselection.AdSelectionManager::class.java)
-            `when`(spyContext.getSystemService(
-                android.adservices.adselection.AdSelectionManager::class.java))
-                .thenReturn(adSelectionManager)
+            // mock the .get() method if using extServices version, otherwise mock getSystemService
+            if (isExtServices) {
+                `when`(android.adservices.adselection.AdSelectionManager.get(any()))
+                    .thenReturn(adSelectionManager)
+            } else {
+                `when`(spyContext.getSystemService(
+                    android.adservices.adselection.AdSelectionManager::class.java))
+                    .thenReturn(adSelectionManager)
+            }
             return adSelectionManager
         }
 
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/appsetid/AppSetIdManagerFuturesTest.kt b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/appsetid/AppSetIdManagerFuturesTest.kt
index a540f73..b92fa9a 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/appsetid/AppSetIdManagerFuturesTest.kt
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/appsetid/AppSetIdManagerFuturesTest.kt
@@ -19,21 +19,24 @@
 import android.content.Context
 import android.os.Looper
 import android.os.OutcomeReceiver
-import android.os.ext.SdkExtensions
-import androidx.annotation.RequiresExtension
 import androidx.privacysandbox.ads.adservices.appsetid.AppSetId
+import androidx.privacysandbox.ads.adservices.java.VersionCompatUtil
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.google.common.truth.Truth.assertThat
 import com.google.common.util.concurrent.ListenableFuture
 import kotlin.test.assertNotEquals
+import org.junit.After
 import org.junit.Assert
 import org.junit.Assume
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
 import org.mockito.ArgumentMatchers.any
 import org.mockito.Mockito.doAnswer
 import org.mockito.Mockito.mock
@@ -43,32 +46,50 @@
 import org.mockito.invocation.InvocationOnMock
 
 @SmallTest
+@SuppressWarnings("NewApi")
 @RunWith(AndroidJUnit4::class)
 @SdkSuppress(minSdkVersion = 30)
 class AppSetIdManagerFuturesTest {
 
+    private var mSession: StaticMockitoSession? = null
+    private val mValidAdExtServicesSdkExtVersion = VersionCompatUtil.isSWithMinExtServicesVersion(9)
+
     @Before
     fun setUp() {
         mContext = spy(ApplicationProvider.getApplicationContext<Context>())
+
+        if (mValidAdExtServicesSdkExtVersion) {
+            // setup a mockitoSession to return the mocked manager
+            // when the static method .get() is called
+            mSession = ExtendedMockito.mockitoSession()
+                .mockStatic(android.adservices.appsetid.AppSetIdManager::class.java)
+                .startMocking()
+        }
+    }
+
+    @After
+    fun tearDown() {
+        mSession?.finishMocking()
     }
 
     @Test
     @SdkSuppress(maxSdkVersion = 33, minSdkVersion = 30)
     fun testAppSetIdOlderVersions() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
-
-        Assume.assumeTrue("maxSdkVersion = API 33 ext 3", sdkExtVersion < 4)
+        Assume.assumeFalse("maxSdkVersion = API 33 ext 3 or API 31/32 ext 8",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion=*/ 4,
+                /* minExtServicesVersion=*/ 9))
         assertThat(AppSetIdManagerFutures.from(mContext)).isEqualTo(null)
     }
 
     @Test
-    @SuppressWarnings("NewApi")
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
     fun testAppSetIdAsync() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion= */ 4,
+                /* minExtServicesVersion=*/ 9))
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
-        val appSetIdManager = mockAppSetIdManager(mContext)
+        val appSetIdManager = mockAppSetIdManager(mContext, mValidAdExtServicesSdkExtVersion)
         setupResponse(appSetIdManager)
         val managerCompat = AppSetIdManagerFutures.from(mContext)
 
@@ -82,19 +103,24 @@
         verify(appSetIdManager).getAppSetId(any(), any())
     }
 
-    @SuppressWarnings("NewApi")
     @SdkSuppress(minSdkVersion = 30)
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
     companion object {
         private lateinit var mContext: Context
 
         private fun mockAppSetIdManager(
-            spyContext: Context
+            spyContext: Context,
+            isExtServices: Boolean
         ): android.adservices.appsetid.AppSetIdManager {
             val appSetIdManager = mock(android.adservices.appsetid.AppSetIdManager::class.java)
-            `when`(spyContext.getSystemService(
-                android.adservices.appsetid.AppSetIdManager::class.java))
-                .thenReturn(appSetIdManager)
+            // mock the .get() method if using extServices version, otherwise mock getSystemService
+            if (isExtServices) {
+                `when`(android.adservices.appsetid.AppSetIdManager.get(ArgumentMatchers.any()))
+                    .thenReturn(appSetIdManager)
+            } else {
+                `when`(spyContext.getSystemService(
+                    android.adservices.appsetid.AppSetIdManager::class.java))
+                    .thenReturn(appSetIdManager)
+            }
             return appSetIdManager
         }
 
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/customaudience/CustomAudienceManagerFuturesTest.kt b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/customaudience/CustomAudienceManagerFuturesTest.kt
index 31b287d..9da1419 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/customaudience/CustomAudienceManagerFuturesTest.kt
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/customaudience/CustomAudienceManagerFuturesTest.kt
@@ -20,8 +20,6 @@
 import android.content.Context
 import android.net.Uri
 import android.os.OutcomeReceiver
-import android.os.ext.SdkExtensions
-import androidx.annotation.RequiresExtension
 import androidx.privacysandbox.ads.adservices.common.AdData
 import androidx.privacysandbox.ads.adservices.common.AdSelectionSignals
 import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
@@ -29,13 +27,17 @@
 import androidx.privacysandbox.ads.adservices.customaudience.JoinCustomAudienceRequest
 import androidx.privacysandbox.ads.adservices.customaudience.LeaveCustomAudienceRequest
 import androidx.privacysandbox.ads.adservices.customaudience.TrustedBiddingData
+import androidx.privacysandbox.ads.adservices.java.VersionCompatUtil
 import androidx.privacysandbox.ads.adservices.java.customaudience.CustomAudienceManagerFutures.Companion.from
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.google.common.truth.Truth
 import java.time.Instant
+import org.junit.After
 import org.junit.Assume
 import org.junit.Before
 import org.junit.Test
@@ -48,6 +50,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.invocation.InvocationOnMock
+import org.mockito.quality.Strictness
 
 @SmallTest
 @SuppressWarnings("NewApi")
@@ -55,27 +58,47 @@
 @SdkSuppress(minSdkVersion = 30)
 class CustomAudienceManagerFuturesTest {
 
+    private var mSession: StaticMockitoSession? = null
+    private val mValidAdExtServicesSdkExtVersion = VersionCompatUtil.isSWithMinExtServicesVersion(9)
+
     @Before
     fun setUp() {
         mContext = spy(ApplicationProvider.getApplicationContext<Context>())
+
+        if (mValidAdExtServicesSdkExtVersion) {
+            // setup a mockitoSession to return the mocked manager
+            // when the static method .get() is called
+            mSession = ExtendedMockito.mockitoSession()
+                .mockStatic(android.adservices.customaudience.CustomAudienceManager::class.java)
+                .strictness(Strictness.LENIENT)
+                .startMocking()
+        }
+    }
+
+    @After
+    fun tearDown() {
+        mSession?.finishMocking()
     }
 
     @Test
     @SdkSuppress(maxSdkVersion = 33, minSdkVersion = 30)
     fun testOlderVersions() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
-
-        Assume.assumeTrue("maxSdkVersion = API 33 ext 3", sdkExtVersion < 4)
+        Assume.assumeFalse("maxSdkVersion = API 33 ext 3 or API 31/32 ext 8",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion=*/ 4,
+                /* minExtServicesVersion=*/ 9))
         Truth.assertThat(from(mContext)).isEqualTo(null)
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
     fun testJoinCustomAudience() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion= */ 4,
+                /* minExtServicesVersion=*/ 9))
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
-        val customAudienceManager = mockCustomAudienceManager(mContext)
+        val customAudienceManager =
+            mockCustomAudienceManager(mContext, mValidAdExtServicesSdkExtVersion)
         setupResponse(customAudienceManager)
         val managerCompat = from(mContext)
 
@@ -100,12 +123,14 @@
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
     fun testLeaveCustomAudience() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion= */ 4,
+                /* minExtServicesVersion=*/ 9))
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
-        val customAudienceManager = mockCustomAudienceManager(mContext)
+        val customAudienceManager =
+            mockCustomAudienceManager(mContext, mValidAdExtServicesSdkExtVersion)
         setupResponse(customAudienceManager)
         val managerCompat = from(mContext)
 
@@ -124,7 +149,6 @@
     }
 
     @SdkSuppress(minSdkVersion = 30)
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
     companion object {
         private lateinit var mContext: Context
         private val uri: Uri = Uri.parse("abc.com")
@@ -138,10 +162,18 @@
         private const val metadata = "metadata"
         private val ads: List<AdData> = listOf(AdData(uri, metadata))
 
-        private fun mockCustomAudienceManager(spyContext: Context): CustomAudienceManager {
+        private fun mockCustomAudienceManager(
+            spyContext: Context,
+            isExtServices: Boolean
+        ): CustomAudienceManager {
             val customAudienceManager = mock(CustomAudienceManager::class.java)
-            `when`(spyContext.getSystemService(CustomAudienceManager::class.java))
-                .thenReturn(customAudienceManager)
+            // mock the .get() method if using extServices version, otherwise mock getSystemService
+            if (isExtServices) {
+                `when`(CustomAudienceManager.get(any())).thenReturn(customAudienceManager)
+            } else {
+                `when`(spyContext.getSystemService(CustomAudienceManager::class.java))
+                    .thenReturn(customAudienceManager)
+            }
             return customAudienceManager
         }
 
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/FledgeCtsDebuggableTest.java b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/FledgeCtsDebuggableTest.java
index 7925188..fdd7e82 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/FledgeCtsDebuggableTest.java
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/FledgeCtsDebuggableTest.java
@@ -26,6 +26,7 @@
 import android.util.Log;
 
 import androidx.annotation.RequiresApi;
+import androidx.javascriptengine.JavaScriptSandbox;
 import androidx.privacysandbox.ads.adservices.adselection.AdSelectionConfig;
 import androidx.privacysandbox.ads.adservices.adselection.AdSelectionManager;
 import androidx.privacysandbox.ads.adservices.adselection.AdSelectionOutcome;
@@ -36,7 +37,7 @@
 import androidx.privacysandbox.ads.adservices.customaudience.CustomAudience;
 import androidx.privacysandbox.ads.adservices.customaudience.JoinCustomAudienceRequest;
 import androidx.privacysandbox.ads.adservices.customaudience.TrustedBiddingData;
-import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo;
+import androidx.privacysandbox.ads.adservices.java.VersionCompatUtil;
 import androidx.privacysandbox.ads.adservices.java.adselection.AdSelectionManagerFutures;
 import androidx.privacysandbox.ads.adservices.java.customaudience.CustomAudienceManagerFutures;
 import androidx.test.core.app.ApplicationProvider;
@@ -175,6 +176,10 @@
         testUtil.disableFledgeEnrollmentCheck(true);
         testUtil.enableAdServiceSystemService(true);
         testUtil.enforceFledgeJsIsolateMaxHeapSize(false);
+
+        if (VersionCompatUtil.INSTANCE.isSWithMinExtServicesVersion(9)) {
+            testUtil.enableBackCompat();
+        }
     }
 
     @AfterClass
@@ -196,10 +201,15 @@
         testUtil.disableFledgeEnrollmentCheck(false);
         testUtil.enableAdServiceSystemService(false);
         testUtil.enforceFledgeJsIsolateMaxHeapSize(true);
+
+        if (VersionCompatUtil.INSTANCE.isSWithMinExtServicesVersion(9)) {
+            testUtil.disableBackCompat();
+        }
     }
 
     @Before
     public void setup() throws Exception {
+        Assume.assumeTrue(JavaScriptSandbox.isSupported());
         mAdSelectionClient =
                 new AdSelectionClient(sContext);
         mCustomAudienceClient =
@@ -211,8 +221,11 @@
 
     @Test
     public void testFledgeAuctionSelectionFlow_overall_Success() throws Exception {
-        // Skip the test if SDK extension 4 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+        // Skip the test if the right SDK extension is not present.
+        Assume.assumeTrue(
+                VersionCompatUtil.INSTANCE.isTestableVersion(
+                        /* minAdServicesVersion=*/ 4,
+                        /* minExtServicesVersion=*/ 9));
 
         List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
         List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
@@ -255,8 +268,11 @@
 
     @Test
     public void testAdSelection_etldViolation_failure() throws Exception {
-        // Skip the test if SDK extension 4 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+        // Skip the test if the right SDK extension is not present.
+        Assume.assumeTrue(
+                VersionCompatUtil.INSTANCE.isTestableVersion(
+                        /* minAdServicesVersion=*/ 4,
+                        /* minExtServicesVersion=*/ 9));
 
         List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
         List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
@@ -310,8 +326,11 @@
 
     @Test
     public void testReportImpression_etldViolation_failure() throws Exception {
-        // Skip the test if SDK extension 4 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+        // Skip the test if the right SDK extension is not present.
+        Assume.assumeTrue(
+                VersionCompatUtil.INSTANCE.isTestableVersion(
+                        /* minAdServicesVersion=*/ 4,
+                        /* minExtServicesVersion=*/ 9));
 
         List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
         List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
@@ -379,8 +398,11 @@
 
     @Test
     public void testAdSelection_skipAdsMalformedBiddingLogic_success() throws Exception {
-        // Skip the test if SDK extension 4 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+        // Skip the test if the right SDK extension is not present.
+        Assume.assumeTrue(
+                VersionCompatUtil.INSTANCE.isTestableVersion(
+                        /* minAdServicesVersion=*/ 4,
+                        /* minExtServicesVersion=*/ 9));
 
         List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
         List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
@@ -432,8 +454,11 @@
 
     @Test
     public void testAdSelection_malformedScoringLogic_failure() throws Exception {
-        // Skip the test if SDK extension 4 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+        // Skip the test if the right SDK extension is not present.
+        Assume.assumeTrue(
+                VersionCompatUtil.INSTANCE.isTestableVersion(
+                        /* minAdServicesVersion=*/ 4,
+                        /* minExtServicesVersion=*/ 9));
 
         List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
         List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
@@ -485,8 +510,11 @@
 
     @Test
     public void testAdSelection_skipAdsFailedGettingBiddingLogic_success() throws Exception {
-        // Skip the test if SDK extension 4 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+        // Skip the test if the right SDK extension is not present.
+        Assume.assumeTrue(
+                VersionCompatUtil.INSTANCE.isTestableVersion(
+                        /* minAdServicesVersion=*/ 4,
+                        /* minExtServicesVersion=*/ 9));
 
         List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
         List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
@@ -536,8 +564,11 @@
 
     @Test
     public void testAdSelection_errorGettingScoringLogic_failure() throws Exception {
-        // Skip the test if SDK extension 4 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+        // Skip the test if the right SDK extension is not present.
+        Assume.assumeTrue(
+                VersionCompatUtil.INSTANCE.isTestableVersion(
+                        /* minAdServicesVersion=*/ 4,
+                        /* minExtServicesVersion=*/ 9));
 
         List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
         List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
@@ -594,8 +625,11 @@
 
     @Test
     public void testAdSelectionFlow_skipNonActivatedCA_Success() throws Exception {
-        // Skip the test if SDK extension 4 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+        // Skip the test if the right SDK extension is not present.
+        Assume.assumeTrue(
+                VersionCompatUtil.INSTANCE.isTestableVersion(
+                        /* minAdServicesVersion=*/ 4,
+                        /* minExtServicesVersion=*/ 9));
 
         List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
         List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
@@ -648,8 +682,11 @@
 
     @Test
     public void testAdSelectionFlow_skipExpiredCA_Success() throws Exception {
-        // Skip the test if SDK extension 4 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+        // Skip the test if the right SDK extension is not present.
+        Assume.assumeTrue(
+                VersionCompatUtil.INSTANCE.isTestableVersion(
+                        /* minAdServicesVersion=*/ 4,
+                        /* minExtServicesVersion=*/ 9));
 
         List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
         List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
@@ -708,8 +745,11 @@
 
     @Test
     public void testAdSelectionFlow_skipCAsThatTimeoutDuringBidding_Success() throws Exception {
-        // Skip the test if SDK extension 4 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+        // Skip the test if the right SDK extension is not present.
+        Assume.assumeTrue(
+                VersionCompatUtil.INSTANCE.isTestableVersion(
+                        /* minAdServicesVersion=*/ 4,
+                        /* minExtServicesVersion=*/ 9));
 
         List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
         List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
@@ -759,8 +799,11 @@
 
     @Test
     public void testAdSelection_overallTimeout_Failure() throws Exception {
-        // Skip the test if SDK extension 4 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+        // Skip the test if the right SDK extension is not present.
+        Assume.assumeTrue(
+                VersionCompatUtil.INSTANCE.isTestableVersion(
+                        /* minAdServicesVersion=*/ 4,
+                        /* minExtServicesVersion=*/ 9));
 
         List<Double> bidsForBuyer1 = ImmutableList.of(1.1, 2.2);
         List<Double> bidsForBuyer2 = ImmutableList.of(4.5, 6.7, 10.0);
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/TestUtil.java b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/TestUtil.java
index 57e0e98..33988c1 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/TestUtil.java
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/TestUtil.java
@@ -92,6 +92,7 @@
     public void overrideAllowlists(boolean override) {
         String overrideStr = override ? "*" : "null";
         runShellCommand("device_config put adservices ppapi_app_allow_list " + overrideStr);
+        runShellCommand("device_config put adservices msmt_api_app_allow_list " + overrideStr);
         runShellCommand("device_config put adservices ppapi_app_signature_allow_list "
                 + overrideStr);
         runShellCommand(
@@ -114,6 +115,18 @@
         }
     }
 
+    public void enableBackCompat() {
+        runShellCommand("device_config put adservices enable_back_compat true");
+        runShellCommand("device_config put adservices consent_source_of_truth 3");
+        runShellCommand("device_config put adservices blocked_topics_source_of_truth 3");
+    }
+
+    public void disableBackCompat() {
+        runShellCommand("device_config put adservices enable_back_compat false");
+        runShellCommand("device_config put adservices consent_source_of_truth null");
+        runShellCommand("device_config put adservices blocked_topics_source_of_truth null");
+    }
+
     // Override measurement related kill switch to ignore the effect of actual PH values.
     // If isOverride = true, override measurement related kill switch to OFF to allow adservices
     // If isOverride = false, override measurement related kill switch to meaningless value so that
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/adid/AdIdManagerTest.java b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/adid/AdIdManagerTest.java
index c35cb46..c7be1d1 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/adid/AdIdManagerTest.java
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/adid/AdIdManagerTest.java
@@ -19,7 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import androidx.privacysandbox.ads.adservices.adid.AdId;
-import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo;
+import androidx.privacysandbox.ads.adservices.java.VersionCompatUtil;
 import androidx.privacysandbox.ads.adservices.java.adid.AdIdManagerFutures;
 import androidx.privacysandbox.ads.adservices.java.endtoend.TestUtil;
 import androidx.test.core.app.ApplicationProvider;
@@ -63,8 +63,11 @@
 
     @Test
     public void testAdId() throws Exception {
-        // Skip the test if SDK extension 4 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+        // Skip the test if the right SDK extension is not present.
+        Assume.assumeTrue(
+                VersionCompatUtil.INSTANCE.isTestableVersion(
+                        /* minAdServicesVersion=*/ 4,
+                        /* minExtServicesVersion=*/ 9));
 
         AdIdManagerFutures adIdManager =
                 AdIdManagerFutures.from(ApplicationProvider.getApplicationContext());
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/appsetid/AppSetIdManagerTest.java b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/appsetid/AppSetIdManagerTest.java
index ecebc1e..fb2fa2f 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/appsetid/AppSetIdManagerTest.java
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/appsetid/AppSetIdManagerTest.java
@@ -19,7 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import androidx.privacysandbox.ads.adservices.appsetid.AppSetId;
-import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo;
+import androidx.privacysandbox.ads.adservices.java.VersionCompatUtil;
 import androidx.privacysandbox.ads.adservices.java.appsetid.AppSetIdManagerFutures;
 import androidx.privacysandbox.ads.adservices.java.endtoend.TestUtil;
 import androidx.test.core.app.ApplicationProvider;
@@ -60,8 +60,11 @@
 
     @Test
     public void testAppSetId() throws Exception {
-        // Skip the test if SDK extension 4 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+        // Skip the test if the right SDK extension is not present.
+        Assume.assumeTrue(
+                VersionCompatUtil.INSTANCE.isTestableVersion(
+                        /* minAdServicesVersion=*/ 4,
+                        /* minExtServicesVersion=*/ 9));
 
         AppSetIdManagerFutures appSetIdManager =
                 AppSetIdManagerFutures.from(ApplicationProvider.getApplicationContext());
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/measurement/MeasurementManagerTest.java b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/measurement/MeasurementManagerTest.java
index 7bc4ea9..c43168e 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/measurement/MeasurementManagerTest.java
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/measurement/MeasurementManagerTest.java
@@ -22,7 +22,7 @@
 
 import android.net.Uri;
 
-import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo;
+import androidx.privacysandbox.ads.adservices.java.VersionCompatUtil;
 import androidx.privacysandbox.ads.adservices.java.endtoend.TestUtil;
 import androidx.privacysandbox.ads.adservices.java.measurement.MeasurementManagerFutures;
 import androidx.privacysandbox.ads.adservices.measurement.DeletionRequest;
@@ -47,6 +47,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 
+@SuppressWarnings("NewApi")
 @RunWith(JUnit4.class)
 @SdkSuppress(minSdkVersion = 28) // API 28 required for device_config used by this test
 // TODO: Consider refactoring so that we're not duplicating code.
@@ -78,6 +79,9 @@
         mTestUtil.overrideDisableMeasurementEnrollmentCheck("1");
         mMeasurementManager =
                 MeasurementManagerFutures.from(ApplicationProvider.getApplicationContext());
+        if (VersionCompatUtil.INSTANCE.isSWithMinExtServicesVersion(9)) {
+            mTestUtil.enableBackCompat();
+        }
 
         // Put in a short sleep to make sure the updated config propagates
         // before starting the tests
@@ -92,14 +96,21 @@
         mTestUtil.overrideMeasurementKillSwitches(false);
         mTestUtil.overrideAdIdKillSwitch(false);
         mTestUtil.overrideDisableMeasurementEnrollmentCheck("0");
+        if (VersionCompatUtil.INSTANCE.isSWithMinExtServicesVersion(9)) {
+            mTestUtil.disableBackCompat();
+        }
+
         // Cool-off rate limiter
         TimeUnit.SECONDS.sleep(1);
     }
 
     @Test
     public void testRegisterSource_NoServerSetup_NoErrors() throws Exception {
-        // Skip the test if SDK extension 5 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 5);
+        // Skip the test if the right SDK extension is not present.
+        Assume.assumeTrue(
+                VersionCompatUtil.INSTANCE.isTestableVersion(
+                        /* minAdServicesVersion=*/ 5,
+                        /* minExtServicesVersion=*/ 9));
 
         assertThat(mMeasurementManager.registerSourceAsync(
                 SOURCE_REGISTRATION_URI,
@@ -109,8 +120,12 @@
 
     @Test
     public void testRegisterAppSources_NoServerSetup_NoErrors() throws Exception {
+        // Skip the test if the right SDK extension is not present
+        Assume.assumeTrue(
+                VersionCompatUtil.INSTANCE.isTestableVersion(
+                        /* minAdServicesVersion=*/ 5,
+                        /* minExtServicesVersion=*/ 9));
         // Skip the test if SDK extension 5 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 5);
 
         SourceRegistrationRequest request =
                 new SourceRegistrationRequest.Builder(
@@ -122,18 +137,23 @@
 
     @Test
     public void testRegisterTrigger_NoServerSetup_NoErrors() throws Exception {
-        // Skip the test if SDK extension 5 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 5);
+        // Skip the test if the right SDK extension is not present.
+        Assume.assumeTrue(
+                VersionCompatUtil.INSTANCE.isTestableVersion(
+                        /* minAdServicesVersion=*/ 5,
+                        /* minExtServicesVersion=*/ 9));
 
         assertThat(mMeasurementManager.registerTriggerAsync(TRIGGER_REGISTRATION_URI).get())
                 .isNotNull();
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 33)
     public void registerWebSource_NoErrors() throws Exception {
-        // Skip the test if SDK extension 5 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 5);
+        // Skip the test if the right SDK extension is not present.
+        Assume.assumeTrue(
+                VersionCompatUtil.INSTANCE.isTestableVersion(
+                        /* minAdServicesVersion=*/ 5,
+                        /* minExtServicesVersion=*/ 9));
 
         WebSourceParams webSourceParams =
                 new WebSourceParams(SOURCE_REGISTRATION_URI, false);
@@ -152,10 +172,12 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 33)
     public void registerWebTrigger_NoErrors() throws Exception {
-        // Skip the test if SDK extension 5 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 5);
+        // Skip the test if the right SDK extension is not present.
+        Assume.assumeTrue(
+                VersionCompatUtil.INSTANCE.isTestableVersion(
+                        /* minAdServicesVersion=*/ 5,
+                        /* minExtServicesVersion=*/ 9));
 
         WebTriggerParams webTriggerParams =
                 new WebTriggerParams(TRIGGER_REGISTRATION_URI, false);
@@ -169,11 +191,12 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 33)
     public void testDeleteRegistrations_withRequest_withNoRange_withCallback_NoErrors()
             throws Exception {
         // Skip the test if SDK extension 5 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 5);
+        // This test should not run for back compat because it depends on adServices running in
+        // system server
+        Assume.assumeTrue(VersionCompatUtil.INSTANCE.isTPlusWithMinAdServicesVersion(5));
 
         DeletionRequest deletionRequest =
                 new DeletionRequest.Builder(
@@ -187,11 +210,13 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 33)
     public void testDeleteRegistrations_withRequest_withEmptyLists_withRange_withCallback_NoErrors()
             throws Exception {
-        // Skip the test if SDK extension 5 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 5);
+        // Skip the test if the right SDK extension is not present.
+        Assume.assumeTrue(
+                VersionCompatUtil.INSTANCE.isTestableVersion(
+                        /* minAdServicesVersion=*/ 5,
+                        /* minExtServicesVersion=*/ 9));
 
         DeletionRequest deletionRequest =
                 new DeletionRequest.Builder(
@@ -207,11 +232,13 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 33)
     public void testDeleteRegistrations_withRequest_withInvalidArguments_withCallback_hasError()
             throws Exception {
-        // Skip the test if SDK extension 5 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 5);
+        // Skip the test if the right SDK extension is not present.
+        Assume.assumeTrue(
+                VersionCompatUtil.INSTANCE.isTestableVersion(
+                        /* minAdServicesVersion=*/ 5,
+                        /* minExtServicesVersion=*/ 9));
 
         DeletionRequest deletionRequest =
                 new DeletionRequest.Builder(
@@ -230,10 +257,12 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 33)
     public void testMeasurementApiStatus_returnResultStatus() throws Exception {
-        // Skip the test if SDK extension 5 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 5);
+        // Skip the test if the right SDK extension is not present.
+        Assume.assumeTrue(
+                VersionCompatUtil.INSTANCE.isTestableVersion(
+                        /* minAdServicesVersion=*/ 5,
+                        /* minExtServicesVersion=*/ 9));
 
         int result = mMeasurementManager.getMeasurementApiStatusAsync().get();
         assertThat(result).isEqualTo(1);
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/topics/TopicsManagerTest.java b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/topics/TopicsManagerTest.java
index 266394a..9b46fff 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/topics/TopicsManagerTest.java
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/topics/TopicsManagerTest.java
@@ -18,7 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo;
+import androidx.privacysandbox.ads.adservices.java.VersionCompatUtil;
 import androidx.privacysandbox.ads.adservices.java.endtoend.TestUtil;
 import androidx.privacysandbox.ads.adservices.java.topics.TopicsManagerFutures;
 import androidx.privacysandbox.ads.adservices.topics.GetTopicsRequest;
@@ -73,6 +73,10 @@
         mTestUtil.shouldForceUseBundledFiles(true);
         // Enable verbose logging.
         mTestUtil.enableVerboseLogging();
+
+        if (VersionCompatUtil.INSTANCE.isSWithMinExtServicesVersion(9)) {
+            mTestUtil.enableBackCompat();
+        }
     }
 
     @After
@@ -84,13 +88,19 @@
         mTestUtil.overrideAllowlists(false);
         mTestUtil.enableEnrollmentCheck(false);
         mTestUtil.shouldForceUseBundledFiles(false);
+        if (VersionCompatUtil.INSTANCE.isSWithMinExtServicesVersion(9)) {
+            mTestUtil.disableBackCompat();
+        }
     }
 
     @Ignore // b/278931615
     @Test
     public void testTopicsManager_runClassifier() throws Exception {
-        // Skip the test if SDK extension 4 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+        // Skip the test if the right SDK extension is not present.
+        Assume.assumeTrue(
+                VersionCompatUtil.INSTANCE.isTestableVersion(
+                        /* minAdServicesVersion=*/ 4,
+                        /* minExtServicesVersion=*/ 9));
 
         TopicsManagerFutures topicsManager =
                 TopicsManagerFutures.from(ApplicationProvider.getApplicationContext());
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/measurement/MeasurementManagerFuturesTest.kt b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/measurement/MeasurementManagerFuturesTest.kt
index 0106e76..cb58c43 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/measurement/MeasurementManagerFuturesTest.kt
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/measurement/MeasurementManagerFuturesTest.kt
@@ -21,10 +21,9 @@
 import android.net.Uri
 import android.os.Looper
 import android.os.OutcomeReceiver
-import android.os.ext.SdkExtensions
 import android.view.InputEvent
-import androidx.annotation.RequiresExtension
 import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+import androidx.privacysandbox.ads.adservices.java.VersionCompatUtil
 import androidx.privacysandbox.ads.adservices.java.measurement.MeasurementManagerFutures.Companion.from
 import androidx.privacysandbox.ads.adservices.measurement.DeletionRequest
 import androidx.privacysandbox.ads.adservices.measurement.SourceRegistrationRequest
@@ -36,6 +35,8 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.google.common.truth.Truth.assertThat
 import java.time.Instant
 import java.util.concurrent.ExecutionException
@@ -46,6 +47,7 @@
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withContext
+import org.junit.After
 import org.junit.Assume
 import org.junit.Before
 import org.junit.Test
@@ -61,6 +63,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.invocation.InvocationOnMock
+import org.mockito.quality.Strictness
 
 @SmallTest
 @SuppressWarnings("NewApi")
@@ -68,27 +71,46 @@
 @SdkSuppress(minSdkVersion = 30)
 class MeasurementManagerFuturesTest {
 
+    private var mSession: StaticMockitoSession? = null
+    private val mValidAdExtServicesSdkExtVersion = VersionCompatUtil.isSWithMinExtServicesVersion(9)
+
     @Before
     fun setUp() {
         mContext = spy(ApplicationProvider.getApplicationContext<Context>())
+
+        if (mValidAdExtServicesSdkExtVersion) {
+            // setup a mockitoSession to return the mocked manager
+            // when the static method .get() is called
+            mSession = ExtendedMockito.mockitoSession()
+                .mockStatic(android.adservices.measurement.MeasurementManager::class.java)
+                .strictness(Strictness.LENIENT)
+                .startMocking()
+        }
+    }
+
+    @After
+    fun tearDown() {
+        mSession?.finishMocking()
     }
 
     @Test
     @SdkSuppress(maxSdkVersion = 33, minSdkVersion = 30)
     fun testMeasurementOlderVersions() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
-
-        Assume.assumeTrue("maxSdkVersion = API 33 ext 4", sdkExtVersion < 5)
+        Assume.assumeFalse("maxSdkVersion = API 33 ext 4 or API 31/32 ext 8",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion=*/ 5,
+                /* minExtServicesVersion=*/ 9))
         assertThat(from(mContext)).isEqualTo(null)
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testDeleteRegistrationsAsync() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5 or API 31/32 ext 9",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion= */ 5,
+                /* minExtServicesVersion=*/ 9))
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
-        val measurementManager = mockMeasurementManager(mContext)
+        val mMeasurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersion)
         val managerCompat = from(mContext)
 
         // Set up the request.
@@ -98,7 +120,7 @@
             assertNotEquals(Looper.myLooper(), Looper.getMainLooper())
             null
         }
-        doAnswer(answer).`when`(measurementManager).deleteRegistrations(any(), any(), any())
+        doAnswer(answer).`when`(mMeasurementManager).deleteRegistrations(any(), any(), any())
 
         // Actually invoke the compat code.
         val request = DeletionRequest(
@@ -115,28 +137,30 @@
         val captor = ArgumentCaptor.forClass(
             android.adservices.measurement.DeletionRequest::class.java
         )
-        verify(measurementManager).deleteRegistrations(captor.capture(), any(), any())
+        verify(mMeasurementManager).deleteRegistrations(captor.capture(), any(), any())
 
         // Verify that the request that the compat code makes to the platform is correct.
         verifyDeletionRequest(captor.value)
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testRegisterSourceAsync() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5 or API 31/32 ext 9",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion= */ 5,
+                /* minExtServicesVersion=*/ 9))
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
+        val mMeasurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersion)
         val inputEvent = mock(InputEvent::class.java)
-        val measurementManager = mockMeasurementManager(mContext)
         val managerCompat = from(mContext)
+
         val answer = { args: InvocationOnMock ->
             assertNotEquals(Looper.myLooper(), Looper.getMainLooper())
             val receiver = args.getArgument<OutcomeReceiver<Any, Exception>>(3)
             receiver.onResult(Object())
             null
         }
-        doAnswer(answer).`when`(measurementManager).registerSource(any(), any(), any(), any())
+        doAnswer(answer).`when`(mMeasurementManager).registerSource(any(), any(), any(), any())
 
         // Actually invoke the compat code.
         managerCompat!!.registerSourceAsync(uri1, inputEvent).get()
@@ -144,7 +168,7 @@
         // Verify that the compat code was invoked correctly.
         val captor1 = ArgumentCaptor.forClass(Uri::class.java)
         val captor2 = ArgumentCaptor.forClass(InputEvent::class.java)
-        verify(measurementManager).registerSource(
+        verify(mMeasurementManager).registerSource(
             captor1.capture(),
             captor2.capture(),
             any(),
@@ -156,27 +180,29 @@
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testRegisterTriggerAsync() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5 or API 31/32 ext 9",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion= */ 5,
+                /* minExtServicesVersion=*/ 9))
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
-        val measurementManager = mockMeasurementManager(mContext)
+        val mMeasurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersion)
         val managerCompat = from(mContext)
+
         val answer = { args: InvocationOnMock ->
             assertNotEquals(Looper.myLooper(), Looper.getMainLooper())
             val receiver = args.getArgument<OutcomeReceiver<Any, Exception>>(2)
             receiver.onResult(Object())
             null
         }
-        doAnswer(answer).`when`(measurementManager).registerTrigger(any(), any(), any())
+        doAnswer(answer).`when`(mMeasurementManager).registerTrigger(any(), any(), any())
 
         // Actually invoke the compat code.
         managerCompat!!.registerTriggerAsync(uri1).get()
 
         // Verify that the compat code was invoked correctly.
         val captor1 = ArgumentCaptor.forClass(Uri::class.java)
-        verify(measurementManager).registerTrigger(
+        verify(mMeasurementManager).registerTrigger(
             captor1.capture(),
             any(),
             any())
@@ -186,20 +212,22 @@
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testRegisterWebSourceAsync() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5 or API 31/32 ext 9",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion= */ 5,
+                /* minExtServicesVersion=*/ 9))
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
-        val measurementManager = mockMeasurementManager(mContext)
+        val mMeasurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersion)
         val managerCompat = from(mContext)
+
         val answer = { args: InvocationOnMock ->
             assertNotEquals(Looper.myLooper(), Looper.getMainLooper())
             val receiver = args.getArgument<OutcomeReceiver<Any, Exception>>(2)
             receiver.onResult(Object())
             null
         }
-        doAnswer(answer).`when`(measurementManager).registerWebSource(any(), any(), any())
+        doAnswer(answer).`when`(mMeasurementManager).registerWebSource(any(), any(), any())
 
         val request = WebSourceRegistrationRequest.Builder(
             listOf(WebSourceParams(uri2, false)), uri1)
@@ -212,7 +240,7 @@
         // Verify that the compat code was invoked correctly.
         val captor1 = ArgumentCaptor.forClass(
             android.adservices.measurement.WebSourceRegistrationRequest::class.java)
-        verify(measurementManager).registerWebSource(
+        verify(mMeasurementManager).registerWebSource(
             captor1.capture(),
             any(),
             any())
@@ -226,20 +254,22 @@
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testRegisterWebTriggerAsync() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5 or API 31/32 ext 9",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion= */ 5,
+                /* minExtServicesVersion=*/ 9))
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
-        val measurementManager = mockMeasurementManager(mContext)
+        val mMeasurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersion)
         val managerCompat = from(mContext)
+
         val answer = { args: InvocationOnMock ->
             assertNotEquals(Looper.myLooper(), Looper.getMainLooper())
             val receiver = args.getArgument<OutcomeReceiver<Any, Exception>>(2)
             receiver.onResult(Object())
             null
         }
-        doAnswer(answer).`when`(measurementManager).registerWebTrigger(any(), any(), any())
+        doAnswer(answer).`when`(mMeasurementManager).registerWebTrigger(any(), any(), any())
 
         val request = WebTriggerRegistrationRequest(listOf(WebTriggerParams(uri1, false)), uri2)
 
@@ -249,7 +279,7 @@
         // Verify that the compat code was invoked correctly.
         val captor1 = ArgumentCaptor.forClass(
             android.adservices.measurement.WebTriggerRegistrationRequest::class.java)
-        verify(measurementManager).registerWebTrigger(
+        verify(mMeasurementManager).registerWebTrigger(
             captor1.capture(),
             any(),
             any())
@@ -263,13 +293,15 @@
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testMeasurementApiStatusAsync() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5 or API 31/32 ext 9",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion= */ 5,
+                /* minExtServicesVersion=*/ 9))
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
-        val measurementManager = mockMeasurementManager(mContext)
+        val mMeasurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersion)
         val managerCompat = from(mContext)
+
         val state = MeasurementManager.MEASUREMENT_API_STATE_DISABLED
         val answer = { args: InvocationOnMock ->
             assertNotEquals(Looper.myLooper(), Looper.getMainLooper())
@@ -277,14 +309,14 @@
             receiver.onResult(state)
             null
         }
-        doAnswer(answer).`when`(measurementManager).getMeasurementApiStatus(any(), any())
+        doAnswer(answer).`when`(mMeasurementManager).getMeasurementApiStatus(any(), any())
 
         // Actually invoke the compat code.
         val result = managerCompat!!.getMeasurementApiStatusAsync()
         result.get()
 
         // Verify that the compat code was invoked correctly.
-        verify(measurementManager).getMeasurementApiStatus(any(), any())
+        verify(mMeasurementManager).getMeasurementApiStatus(any(), any())
 
         // Verify that the result.
         assertThat(result.get() == state)
@@ -292,21 +324,23 @@
 
     @ExperimentalFeatures.RegisterSourceOptIn
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testRegisterSourceAsync_allSuccess() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5 or API 31/32 ext 9",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion= */ 5,
+                /* minExtServicesVersion=*/ 9))
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
+        val mMeasurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersion)
         val inputEvent = mock(InputEvent::class.java)
-        val measurementManager = mockMeasurementManager(mContext)
         val managerCompat = from(mContext)
+
         val successCallback = { args: InvocationOnMock ->
             assertNotEquals(Looper.myLooper(), Looper.getMainLooper())
             val receiver = args.getArgument<OutcomeReceiver<Any, Exception>>(3)
             receiver.onResult(Object())
             null
         }
-        doAnswer(successCallback).`when`(measurementManager)
+        doAnswer(successCallback).`when`(mMeasurementManager)
             .registerSource(any(), any(), any(), any())
 
         // Actually invoke the compat code.
@@ -316,12 +350,12 @@
         managerCompat!!.registerSourceAsync(request).get()
 
         // Verify that the compat code was invoked correctly.
-        verify(measurementManager).registerSource(
+        verify(mMeasurementManager).registerSource(
             eq(uri1),
             eq(inputEvent),
             any(Executor::class.java),
             any())
-        verify(measurementManager).registerSource(
+        verify(mMeasurementManager).registerSource(
             eq(uri2),
             eq(inputEvent),
             any(Executor::class.java),
@@ -329,15 +363,17 @@
     }
 
     @ExperimentalFeatures.RegisterSourceOptIn
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     @Test
     fun testRegisterSource_15thOf20Fails_atLeast15thExecutes() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5 or API 31/32 ext 9",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion= */ 5,
+                /* minExtServicesVersion=*/ 9))
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
-        val measurementManager = mockMeasurementManager(mContext)
+        val mMeasurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersion)
         val mockInputEvent = mock(InputEvent::class.java)
         val managerCompat = from(mContext)
+
         val successCallback = { args: InvocationOnMock ->
             val receiver = args.getArgument<OutcomeReceiver<Any, Exception>>(3)
             receiver.onResult(Object())
@@ -354,10 +390,10 @@
         val uris = (1..20).map { i ->
             val uri = Uri.parse("www.uri$i.com")
             if (i == 15) {
-                doAnswer(errorCallback).`when`(measurementManager)
+                doAnswer(errorCallback).`when`(mMeasurementManager)
                     .registerSource(eq(uri), any(), any(), any())
             } else {
-                doAnswer(successCallback).`when`(measurementManager)
+                doAnswer(successCallback).`when`(mMeasurementManager)
                     .registerSource(eq(uri), any(), any(), any())
             }
             uri
@@ -382,13 +418,13 @@
         // registerSource gets called 1-20 times. We cannot predict the exact number because
         // uri15 would crash asynchronously. Other uris may succeed and those threads on default
         // dispatcher won't crash.
-        verify(measurementManager, atLeastOnce()).registerSource(
+        verify(mMeasurementManager, atLeastOnce()).registerSource(
             any(),
             eq(mockInputEvent),
             any(),
             any()
         )
-        verify(measurementManager, atMost(20)).registerSource(
+        verify(mMeasurementManager, atMost(20)).registerSource(
             any(),
             eq(mockInputEvent),
             any(),
@@ -397,17 +433,25 @@
     }
 
     @SdkSuppress(minSdkVersion = 30)
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     companion object {
 
         private val uri1: Uri = Uri.parse("www.abc.com")
         private val uri2: Uri = Uri.parse("http://www.xyz.com")
         private lateinit var mContext: Context
 
-        private fun mockMeasurementManager(spyContext: Context): MeasurementManager {
+        private fun mockMeasurementManager(
+            spyContext: Context,
+            isExtServices: Boolean
+        ): MeasurementManager {
             val measurementManager = mock(MeasurementManager::class.java)
-            `when`(spyContext.getSystemService(MeasurementManager::class.java))
-                .thenReturn(measurementManager)
+            // mock the .get() method if using extServices version, otherwise mock getSystemService
+            if (isExtServices) {
+                `when`(android.adservices.measurement.MeasurementManager.get(any()))
+                    .thenReturn(measurementManager)
+            } else {
+                `when`(spyContext.getSystemService(MeasurementManager::class.java))
+                    .thenReturn(measurementManager)
+            }
             return measurementManager
         }
 
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/topics/TopicsManagerFuturesTest.kt b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/topics/TopicsManagerFuturesTest.kt
index c44c32f..6d84365 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/topics/TopicsManagerFuturesTest.kt
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/topics/TopicsManagerFuturesTest.kt
@@ -20,8 +20,7 @@
 import android.adservices.topics.TopicsManager
 import android.content.Context
 import android.os.OutcomeReceiver
-import android.os.ext.SdkExtensions
-import androidx.annotation.RequiresExtension
+import androidx.privacysandbox.ads.adservices.java.VersionCompatUtil
 import androidx.privacysandbox.ads.adservices.java.topics.TopicsManagerFutures.Companion.from
 import androidx.privacysandbox.ads.adservices.topics.GetTopicsRequest
 import androidx.privacysandbox.ads.adservices.topics.GetTopicsResponse
@@ -29,8 +28,11 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.google.common.truth.Truth.assertThat
 import com.google.common.util.concurrent.ListenableFuture
+import org.junit.After
 import org.junit.Assert
 import org.junit.Assume
 import org.junit.Before
@@ -44,6 +46,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.invocation.InvocationOnMock
+import org.mockito.quality.Strictness
 
 @SmallTest
 @SuppressWarnings("NewApi")
@@ -51,43 +54,65 @@
 @SdkSuppress(minSdkVersion = 30)
 class TopicsManagerFuturesTest {
 
+    private var mSession: StaticMockitoSession? = null
+    private val mValidAdExtServicesSdkExtVersion = VersionCompatUtil.isSWithMinExtServicesVersion(9)
+
     @Before
     fun setUp() {
         mContext = spy(ApplicationProvider.getApplicationContext<Context>())
+
+        if (mValidAdExtServicesSdkExtVersion) {
+            // setup a mockitoSession to return the mocked manager
+            // when the static method .get() is called
+            mSession = ExtendedMockito.mockitoSession()
+                .mockStatic(android.adservices.topics.TopicsManager::class.java)
+                .strictness(Strictness.LENIENT)
+                .startMocking()
+        }
+    }
+
+    @After
+    fun tearDown() {
+        mSession?.finishMocking()
     }
 
     @Test
     @SdkSuppress(maxSdkVersion = 33, minSdkVersion = 30)
     fun testTopicsOlderVersions() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
-
-        Assume.assumeTrue("maxSdkVersion = API 33 ext 3", sdkExtVersion < 4)
+        Assume.assumeFalse("maxSdkVersion = API 33 ext 3 or API 31/32 ext 8",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion=*/ 4,
+                /* minExtServicesVersion=*/ 9))
         assertThat(from(mContext)).isEqualTo(null)
     }
 
     @Test
     @SuppressWarnings("NewApi")
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
     fun testTopicsAsync() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion= */ 4,
+                /* minExtServicesVersion=*/ 9))
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
-        val topicsManager = mockTopicsManager(mContext)
+        val topicsManager = mockTopicsManager(mContext, mValidAdExtServicesSdkExtVersion)
         setupTopicsResponse(topicsManager)
         val managerCompat = from(mContext)
 
         // Actually invoke the compat code.
-        val request =
-            GetTopicsRequest.Builder().setAdsSdkName(mSdkName).setShouldRecordObservation(true)
-                .build()
+        val request = GetTopicsRequest.Builder()
+            .setAdsSdkName(mSdkName)
+            .setShouldRecordObservation(true)
+            .build()
 
-        val result: ListenableFuture<GetTopicsResponse> = managerCompat!!.getTopicsAsync(request)
+        val result: ListenableFuture<GetTopicsResponse> =
+            managerCompat!!.getTopicsAsync(request)
 
         // Verify that the result of the compat call is correct.
         verifyResponse(result.get())
 
         // Verify that the compat code was invoked correctly.
-        val captor = ArgumentCaptor.forClass(android.adservices.topics.GetTopicsRequest::class.java)
+        val captor = ArgumentCaptor
+            .forClass(android.adservices.topics.GetTopicsRequest::class.java)
         verify(topicsManager).getTopics(captor.capture(), any(), any())
 
         // Verify that the request that the compat code makes to the platform is correct.
@@ -96,12 +121,13 @@
 
     @Test
     @SuppressWarnings("NewApi")
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testTopicsAsyncPreviewSupported() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5 or API 31/32 ext 9",
+            VersionCompatUtil.isTestableVersion(
+                /* minAdServicesVersion= */ 5,
+                /* minExtServicesVersion=*/ 9))
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
-        val topicsManager = mockTopicsManager(mContext)
+        val topicsManager = mockTopicsManager(mContext, mValidAdExtServicesSdkExtVersion)
         setupTopicsResponse(topicsManager)
         val managerCompat = from(mContext)
 
@@ -110,13 +136,15 @@
             GetTopicsRequest.Builder().setAdsSdkName(mSdkName).setShouldRecordObservation(false)
                 .build()
 
-        val result: ListenableFuture<GetTopicsResponse> = managerCompat!!.getTopicsAsync(request)
+        val result: ListenableFuture<GetTopicsResponse> =
+            managerCompat!!.getTopicsAsync(request)
 
         // Verify that the result of the compat call is correct.
         verifyResponse(result.get())
 
         // Verify that the compat code was invoked correctly.
-        val captor = ArgumentCaptor.forClass(android.adservices.topics.GetTopicsRequest::class.java)
+        val captor =
+            ArgumentCaptor.forClass(android.adservices.topics.GetTopicsRequest::class.java)
         verify(topicsManager).getTopics(captor.capture(), any(), any())
 
         // Verify that the request that the compat code makes to the platform is correct.
@@ -127,14 +155,18 @@
         private lateinit var mContext: Context
         private val mSdkName: String = "sdk1"
 
-        @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
-        private fun mockTopicsManager(spyContext: Context): TopicsManager {
+        private fun mockTopicsManager(spyContext: Context, isExtServices: Boolean): TopicsManager {
             val topicsManager = mock(TopicsManager::class.java)
-            `when`(spyContext.getSystemService(TopicsManager::class.java)).thenReturn(topicsManager)
+            // mock the .get() method if using extServices version, otherwise mock getSystemService
+            if (isExtServices) {
+                `when`(TopicsManager.get(any())).thenReturn(topicsManager)
+            } else {
+                `when`(spyContext.getSystemService(TopicsManager::class.java))
+                    .thenReturn(topicsManager)
+            }
             return topicsManager
         }
 
-        @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
         private fun setupTopicsResponse(topicsManager: TopicsManager) {
             // Set up the response that TopicsManager will return when the compat code calls it.
             val topic1 = Topic(1, 1, 1)
@@ -152,7 +184,6 @@
             )
         }
 
-        @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
         private fun verifyRequest(topicsRequest: android.adservices.topics.GetTopicsRequest) {
             // Set up the request that we expect the compat code to invoke.
             val expectedRequest =
@@ -161,7 +192,6 @@
             Assert.assertEquals(expectedRequest.adsSdkName, topicsRequest.adsSdkName)
         }
 
-        @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
         private fun verifyRequestPreviewApi(
             topicsRequest: android.adservices.topics.GetTopicsRequest
         ) {
@@ -176,7 +206,6 @@
             )
         }
 
-        @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
         private fun verifyResponse(getTopicsResponse: GetTopicsResponse) {
             Assert.assertEquals(2, getTopicsResponse.topics.size)
             val topic1 = getTopicsResponse.topics[0]
diff --git a/privacysandbox/ads/ads-adservices/build.gradle b/privacysandbox/ads/ads-adservices/build.gradle
index 866cc02..1c3fa23 100644
--- a/privacysandbox/ads/ads-adservices/build.gradle
+++ b/privacysandbox/ads/ads-adservices/build.gradle
@@ -41,6 +41,7 @@
 
     androidTestImplementation(libs.mockitoCore4, excludes.bytebuddy) // DexMaker has it"s own MockMaker
     androidTestImplementation(libs.dexmakerMockitoInline, excludes.bytebuddy) // DexMaker has it"s own MockMaker
+    androidTestImplementation(libs.dexmakerMockitoInlineExtended)
 }
 
 android {
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adid/AdIdManagerTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adid/AdIdManagerTest.kt
index 197f46b..ce7e661 100644
--- a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adid/AdIdManagerTest.kt
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adid/AdIdManagerTest.kt
@@ -20,14 +20,18 @@
 import android.os.OutcomeReceiver
 import android.os.ext.SdkExtensions
 import androidx.annotation.RequiresExtension
+import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.runBlocking
+import org.junit.After
 import org.junit.Assert
-import org.junit.Assume.assumeTrue
+import org.junit.Assume
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -44,27 +48,42 @@
 @RunWith(AndroidJUnit4::class)
 @SdkSuppress(minSdkVersion = 30)
 class AdIdManagerTest {
+    private var mSession: StaticMockitoSession? = null
+    private val mValidAdServicesSdkExtVersion = AdServicesInfo.adServicesVersion() >= 4
+    private val mValidAdExtServicesSdkExtVersion = AdServicesInfo.extServicesVersion() >= 9
 
     @Before
     fun setUp() {
         mContext = spy(ApplicationProvider.getApplicationContext<Context>())
+
+        if (mValidAdExtServicesSdkExtVersion) {
+            // setup a mockitoSession to return the mocked manager
+            // when the static method .get() is called
+            mSession = ExtendedMockito.mockitoSession()
+                .mockStatic(android.adservices.adid.AdIdManager::class.java)
+                .startMocking();
+        }
+    }
+
+    @After
+    fun tearDown() {
+        mSession?.finishMocking()
     }
 
     @Test
     @SdkSuppress(maxSdkVersion = 33, minSdkVersion = 30)
     fun testAdIdOlderVersions() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
-        assumeTrue("maxSdkVersion = API 33 ext 3", sdkExtVersion < 4)
+        Assume.assumeTrue("maxSdkVersion = API 33 ext 3", !mValidAdServicesSdkExtVersion)
+        Assume.assumeTrue("maxSdkVersion = API 31/32 ext 8", !mValidAdExtServicesSdkExtVersion)
         assertThat(AdIdManager.obtain(mContext)).isEqualTo(null)
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
     fun testAdIdAsync() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+            mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
 
-        assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
-        val adIdManager = mockAdIdManager(mContext)
+        val adIdManager = mockAdIdManager(mContext, mValidAdExtServicesSdkExtVersion)
         setupResponse(adIdManager)
         val managerCompat = AdIdManager.obtain(mContext)
 
@@ -85,10 +104,19 @@
     companion object {
         private lateinit var mContext: Context
 
-        private fun mockAdIdManager(spyContext: Context): android.adservices.adid.AdIdManager {
+        private fun mockAdIdManager(
+            spyContext: Context,
+            isExtServices: Boolean
+        ): android.adservices.adid.AdIdManager {
             val adIdManager = mock(android.adservices.adid.AdIdManager::class.java)
-            `when`(spyContext.getSystemService(android.adservices.adid.AdIdManager::class.java))
-                .thenReturn(adIdManager)
+            // only mock the .get() method if using extServices version
+            if (isExtServices) {
+                `when`(android.adservices.adid.AdIdManager.get(any()))
+                    .thenReturn(adIdManager)
+            } else {
+                `when`(spyContext.getSystemService(android.adservices.adid.AdIdManager::class.java))
+                    .thenReturn(adIdManager)
+            }
             return adIdManager
         }
 
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManagerTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManagerTest.kt
index 8d71ea2..8f4b4a24 100644
--- a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManagerTest.kt
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManagerTest.kt
@@ -20,17 +20,19 @@
 import android.content.Context
 import android.net.Uri
 import android.os.OutcomeReceiver
-import android.os.ext.SdkExtensions
-import androidx.annotation.RequiresExtension
 import androidx.privacysandbox.ads.adservices.adselection.AdSelectionManager.Companion.obtain
 import androidx.privacysandbox.ads.adservices.common.AdSelectionSignals
 import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
+import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.runBlocking
+import org.junit.After
 import org.junit.Assert
 import org.junit.Assume
 import org.junit.Before
@@ -44,33 +46,50 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.invocation.InvocationOnMock
+import org.mockito.quality.Strictness
 
 @SmallTest
 @SuppressWarnings("NewApi")
 @RunWith(AndroidJUnit4::class)
 @SdkSuppress(minSdkVersion = 30)
 class AdSelectionManagerTest {
+    private var mSession: StaticMockitoSession? = null
+    private val mValidAdServicesSdkExtVersion = AdServicesInfo.adServicesVersion() >= 4
+    private val mValidAdExtServicesSdkExtVersion = AdServicesInfo.extServicesVersion() >= 9
+
     @Before
     fun setUp() {
         mContext = spy(ApplicationProvider.getApplicationContext<Context>())
+
+        if (mValidAdExtServicesSdkExtVersion) {
+            // setup a mockitoSession to return the mocked manager
+            // when the static method .get() is called
+            mSession = mockitoSession()
+                .mockStatic(android.adservices.adselection.AdSelectionManager::class.java)
+                .strictness(Strictness.LENIENT)
+                .startMocking();
+        }
+    }
+
+    @After
+    fun tearDown() {
+        mSession?.finishMocking()
     }
 
     @Test
     @SdkSuppress(maxSdkVersion = 33, minSdkVersion = 30)
     fun testAdSelectionOlderVersions() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
-
-        Assume.assumeTrue("maxSdkVersion = API 33 ext 3", sdkExtVersion < 4)
+        Assume.assumeTrue("maxSdkVersion = API 33 ext 3", !mValidAdServicesSdkExtVersion)
+        Assume.assumeTrue("maxSdkVersion = API 31/32 ext 8", !mValidAdExtServicesSdkExtVersion)
         assertThat(obtain(mContext)).isEqualTo(null)
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
     fun testSelectAds() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+            mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
-        val adSelectionManager = mockAdSelectionManager(mContext)
+        val adSelectionManager = mockAdSelectionManager(mContext, mValidAdExtServicesSdkExtVersion)
         setupAdSelectionResponse(adSelectionManager)
         val managerCompat = obtain(mContext)
 
@@ -92,13 +111,13 @@
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
     fun testReportImpression() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+            mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
-        val adSelectionManager = mockAdSelectionManager(mContext)
+        val adSelectionManager = mockAdSelectionManager(mContext, mValidAdExtServicesSdkExtVersion)
         setupAdSelectionResponse(adSelectionManager)
+
         val managerCompat = obtain(mContext)
         val reportImpressionRequest = ReportImpressionRequest(adSelectionId, adSelectionConfig)
 
@@ -117,7 +136,6 @@
     }
 
     @SdkSuppress(minSdkVersion = 30)
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
     companion object {
         private lateinit var mContext: Context
         private const val adSelectionId = 1234L
@@ -146,13 +164,20 @@
         private val renderUri = Uri.parse("render-uri.com")
 
         private fun mockAdSelectionManager(
-            spyContext: Context
+            spyContext: Context,
+            isExtServices: Boolean
         ): android.adservices.adselection.AdSelectionManager {
             val adSelectionManager =
                 mock(android.adservices.adselection.AdSelectionManager::class.java)
             `when`(spyContext.getSystemService(
                 android.adservices.adselection.AdSelectionManager::class.java))
                 .thenReturn(adSelectionManager)
+            // only mock the .get() method if using extServices version
+            if (isExtServices) {
+                `when`(android.adservices.adselection.AdSelectionManager.get(any()))
+                    .thenReturn(adSelectionManager)
+            }
+
             return adSelectionManager
         }
 
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/appsetid/AppSetIdManagerTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/appsetid/AppSetIdManagerTest.kt
index f3f3e99..8167b8f 100644
--- a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/appsetid/AppSetIdManagerTest.kt
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/appsetid/AppSetIdManagerTest.kt
@@ -20,12 +20,16 @@
 import android.os.OutcomeReceiver
 import android.os.ext.SdkExtensions
 import androidx.annotation.RequiresExtension
+import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.runBlocking
+import org.junit.After
 import org.junit.Assert
 import org.junit.Assume
 import org.junit.Before
@@ -44,28 +48,42 @@
 @RunWith(AndroidJUnit4::class)
 @SdkSuppress(minSdkVersion = 30)
 class AppSetIdManagerTest {
+    private var mSession: StaticMockitoSession? = null
+    private val mValidAdServicesSdkExtVersion = AdServicesInfo.adServicesVersion() >= 4
+    private val mValidAdExtServicesSdkExtVersion = AdServicesInfo.extServicesVersion() >= 9
+
     @Before
     fun setUp() {
         mContext = spy(ApplicationProvider.getApplicationContext<Context>())
+
+        if (mValidAdExtServicesSdkExtVersion) {
+            // setup a mockitoSession to return the mocked manager
+            // when the static method .get() is called
+            mSession = ExtendedMockito.mockitoSession()
+                .mockStatic(android.adservices.appsetid.AppSetIdManager::class.java)
+                .startMocking();
+        }
+    }
+
+    @After
+    fun tearDown() {
+        mSession?.finishMocking()
     }
 
     @Test
     @SdkSuppress(maxSdkVersion = 33, minSdkVersion = 30)
     fun testAppSetIdOlderVersions() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
-
-        Assume.assumeTrue("maxSdkVersion = API 33 ext 3", sdkExtVersion < 4)
+        Assume.assumeTrue("maxSdkVersion = API 33 ext 3", !mValidAdServicesSdkExtVersion)
+        Assume.assumeTrue("maxSdkVersion = API 31/32 ext 8", !mValidAdExtServicesSdkExtVersion)
         assertThat(AppSetIdManager.obtain(mContext)).isEqualTo(null)
     }
 
     @Test
-    @SuppressWarnings("NewApi")
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
     fun testAppSetIdAsync() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+            mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
-        val appSetIdManager = mockAppSetIdManager(mContext)
+        val appSetIdManager = mockAppSetIdManager(mContext, mValidAdExtServicesSdkExtVersion)
         setupResponse(appSetIdManager)
         val managerCompat = AppSetIdManager.obtain(mContext)
 
@@ -87,12 +105,19 @@
         private lateinit var mContext: Context
 
         private fun mockAppSetIdManager(
-            spyContext: Context
+            spyContext: Context,
+            isExtServices: Boolean
         ): android.adservices.appsetid.AppSetIdManager {
             val appSetIdManager = mock(android.adservices.appsetid.AppSetIdManager::class.java)
-            `when`(spyContext.getSystemService(
-                android.adservices.appsetid.AppSetIdManager::class.java))
-                .thenReturn(appSetIdManager)
+            // only mock the .get() method if using extServices version
+            if (isExtServices) {
+                `when`(android.adservices.appsetid.AppSetIdManager.get(any()))
+                    .thenReturn(appSetIdManager)
+            } else {
+                `when`(spyContext.getSystemService(
+                    android.adservices.appsetid.AppSetIdManager::class.java))
+                    .thenReturn(appSetIdManager)
+            }
             return appSetIdManager
         }
 
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManagerTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManagerTest.kt
index a107e7d..53d4f16 100644
--- a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManagerTest.kt
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManagerTest.kt
@@ -20,19 +20,21 @@
 import android.content.Context
 import android.net.Uri
 import android.os.OutcomeReceiver
-import android.os.ext.SdkExtensions
-import androidx.annotation.RequiresExtension
 import androidx.privacysandbox.ads.adservices.common.AdData
 import androidx.privacysandbox.ads.adservices.common.AdSelectionSignals
 import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
 import androidx.privacysandbox.ads.adservices.customaudience.CustomAudienceManager.Companion.obtain
+import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.google.common.truth.Truth
 import java.time.Instant
 import kotlinx.coroutines.runBlocking
+import org.junit.After
 import org.junit.Assume
 import org.junit.Before
 import org.junit.Test
@@ -45,6 +47,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.invocation.InvocationOnMock
+import org.mockito.quality.Strictness
 
 @SmallTest
 @SuppressWarnings("NewApi")
@@ -52,27 +55,44 @@
 @SdkSuppress(minSdkVersion = 30)
 class CustomAudienceManagerTest {
 
+    private var mSession: StaticMockitoSession? = null
+    private val mValidAdServicesSdkExtVersion = AdServicesInfo.adServicesVersion() >= 4
+    private val mValidAdExtServicesSdkExtVersion = AdServicesInfo.extServicesVersion() >= 9
+
     @Before
     fun setUp() {
         mContext = spy(ApplicationProvider.getApplicationContext<Context>())
+
+        if (mValidAdExtServicesSdkExtVersion) {
+            // setup a mockitoSession to return the mocked manager
+            // when the static method .get() is called
+            mSession = ExtendedMockito.mockitoSession()
+                .mockStatic(android.adservices.customaudience.CustomAudienceManager::class.java)
+                .strictness(Strictness.LENIENT)
+                .startMocking()
+        }
+    }
+
+    @After
+    fun tearDown() {
+        mSession?.finishMocking()
     }
 
     @Test
     @SdkSuppress(maxSdkVersion = 33, minSdkVersion = 30)
     fun testOlderVersions() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
-
-        Assume.assumeTrue("maxSdkVersion = API 33 ext 3", sdkExtVersion < 4)
+        Assume.assumeTrue("maxSdkVersion = API 33 ext 3", !mValidAdServicesSdkExtVersion)
+        Assume.assumeTrue("maxSdkVersion = API 31/32 ext 8", !mValidAdExtServicesSdkExtVersion)
         Truth.assertThat(obtain(mContext)).isEqualTo(null)
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
     fun testJoinCustomAudience() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+            mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
-        val customAudienceManager = mockCustomAudienceManager(mContext)
+        val customAudienceManager =
+            mockCustomAudienceManager(mContext, mValidAdExtServicesSdkExtVersion)
         setupResponse(customAudienceManager)
         val managerCompat = obtain(mContext)
 
@@ -99,12 +119,12 @@
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
     fun testLeaveCustomAudience() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+            mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
-        val customAudienceManager = mockCustomAudienceManager(mContext)
+        val customAudienceManager =
+            mockCustomAudienceManager(mContext, mValidAdExtServicesSdkExtVersion)
         setupResponse(customAudienceManager)
         val managerCompat = obtain(mContext)
 
@@ -125,7 +145,6 @@
     }
 
     @SdkSuppress(minSdkVersion = 30)
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
     companion object {
         private lateinit var mContext: Context
         private val uri: Uri = Uri.parse("abc.com")
@@ -139,10 +158,17 @@
         private const val metadata = "metadata"
         private val ads: List<AdData> = listOf(AdData(uri, metadata))
 
-        private fun mockCustomAudienceManager(spyContext: Context): CustomAudienceManager {
+        private fun mockCustomAudienceManager(
+            spyContext: Context,
+            isExtServices: Boolean
+        ): CustomAudienceManager {
             val customAudienceManager = mock(CustomAudienceManager::class.java)
             `when`(spyContext.getSystemService(CustomAudienceManager::class.java))
                 .thenReturn(customAudienceManager)
+            // only mock the .get() method if using extServices version
+            if (isExtServices) {
+                `when`(CustomAudienceManager.get(any())).thenReturn(customAudienceManager)
+            }
             return customAudienceManager
         }
 
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerTest.kt
index 1e0a826..1b9f302 100644
--- a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerTest.kt
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerTest.kt
@@ -20,20 +20,21 @@
 import android.content.Context
 import android.net.Uri
 import android.os.OutcomeReceiver
-import android.os.ext.SdkExtensions
 import android.view.InputEvent
-import androidx.annotation.RequiresExtension
 import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo
 import androidx.privacysandbox.ads.adservices.measurement.MeasurementManager.Companion.obtain
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import androidx.testutils.fail
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.google.common.truth.Truth.assertThat
 import java.time.Instant
-import kotlin.IllegalArgumentException
 import kotlinx.coroutines.runBlocking
+import org.junit.After
 import org.junit.Assume
 import org.junit.Before
 import org.junit.Test
@@ -49,6 +50,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.invocation.InvocationOnMock
+import org.mockito.quality.Strictness
 
 @SmallTest
 @SuppressWarnings("NewApi")
@@ -56,27 +58,43 @@
 @SdkSuppress(minSdkVersion = 30)
 class MeasurementManagerTest {
 
+    private var mSession: StaticMockitoSession? = null
+    private val mValidAdServicesSdkExtVersion = AdServicesInfo.adServicesVersion() >= 5
+    private val mValidAdExtServicesSdkExtVersion = AdServicesInfo.extServicesVersion() >= 9
+
     @Before
     fun setUp() {
         mContext = spy(ApplicationProvider.getApplicationContext<Context>())
+
+        if (mValidAdExtServicesSdkExtVersion) {
+            // setup a mockitoSession to return the mocked manager
+            // when the static method .get() is called
+            mSession = ExtendedMockito.mockitoSession()
+                .mockStatic(android.adservices.measurement.MeasurementManager::class.java)
+                .strictness(Strictness.LENIENT)
+                .startMocking()
+        }
+    }
+
+    @After
+    fun tearDown() {
+        mSession?.finishMocking()
     }
 
     @Test
     @SdkSuppress(maxSdkVersion = 33, minSdkVersion = 30)
     fun testMeasurementOlderVersions() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
-
-        Assume.assumeTrue("maxSdkVersion = API 33 ext 4", sdkExtVersion < 5)
+        Assume.assumeTrue("maxSdkVersion = API 33 ext 4", !mValidAdServicesSdkExtVersion)
+        Assume.assumeTrue("maxSdkVersion = API 31/32 ext 8", !mValidAdExtServicesSdkExtVersion)
         assertThat(obtain(mContext)).isEqualTo(null)
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testDeleteRegistrations() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5 or API 31/32 ext 9",
+            mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
-        val measurementManager = mockMeasurementManager(mContext)
+        val measurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersion)
         val managerCompat = obtain(mContext)
 
         // Set up the request.
@@ -111,14 +129,14 @@
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testRegisterSource() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5 or API 31/32 ext 9",
+            mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
+        val measurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersion)
         val inputEvent = mock(InputEvent::class.java)
-        val measurementManager = mockMeasurementManager(mContext)
         val managerCompat = obtain(mContext)
+
         val answer = { args: InvocationOnMock ->
             val receiver = args.getArgument<OutcomeReceiver<Any, Exception>>(3)
             receiver.onResult(Object())
@@ -146,12 +164,11 @@
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testRegisterTrigger() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5 or API 31/32 ext 9",
+            mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
-        val measurementManager = mockMeasurementManager(mContext)
+        val measurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersion)
         val managerCompat = obtain(mContext)
         val answer = { args: InvocationOnMock ->
             val receiver = args.getArgument<OutcomeReceiver<Any, Exception>>(2)
@@ -177,12 +194,11 @@
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testRegisterWebSource() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5 or API 31/32 ext 9",
+            mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
-        val measurementManager = mockMeasurementManager(mContext)
+        val measurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersion)
         val managerCompat = obtain(mContext)
         val answer = { args: InvocationOnMock ->
             val receiver = args.getArgument<OutcomeReceiver<Any, Exception>>(2)
@@ -218,15 +234,15 @@
     }
 
     @ExperimentalFeatures.RegisterSourceOptIn
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     @Test
     fun testRegisterSource_allSuccess() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5 or API 31/32 ext 9",
+            mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
-        val measurementManager = mockMeasurementManager(mContext)
+        val measurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersion)
         val mockInputEvent = mock(InputEvent::class.java)
         val managerCompat = obtain(mContext)
+
         val successCallback = { args: InvocationOnMock ->
             val receiver = args.getArgument<OutcomeReceiver<Any, Exception>>(3)
             receiver.onResult(Object())
@@ -252,15 +268,15 @@
     }
 
     @ExperimentalFeatures.RegisterSourceOptIn
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     @Test
     fun testRegisterSource_15thOf20Fails_remaining5DoNotExecute() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5 or API 31/32 ext 9",
+            mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
-        val measurementManager = mockMeasurementManager(mContext)
+        val measurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersion)
         val mockInputEvent = mock(InputEvent::class.java)
         val managerCompat = obtain(mContext)
+
         val successCallback = { args: InvocationOnMock ->
             val receiver = args.getArgument<OutcomeReceiver<Any, Exception>>(3)
             receiver.onResult(Object())
@@ -317,12 +333,11 @@
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testRegisterWebTrigger() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5 or API 31/32 ext 9",
+            mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
-        val measurementManager = mockMeasurementManager(mContext)
+        val measurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersion)
         val managerCompat = obtain(mContext)
         val answer = { args: InvocationOnMock ->
             val receiver = args.getArgument<OutcomeReceiver<Any, Exception>>(2)
@@ -356,63 +371,33 @@
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testMeasurementApiStatus() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5 or API 31/32 ext 9",
+            mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
-        val measurementManager = mockMeasurementManager(mContext)
-        val managerCompat = obtain(mContext)
-        val state = MeasurementManager.MEASUREMENT_API_STATE_ENABLED
-        val answer = { args: InvocationOnMock ->
-            val receiver = args.getArgument<OutcomeReceiver<Int, Exception>>(1)
-            receiver.onResult(state)
-            null
-        }
-        doAnswer(answer).`when`(measurementManager).getMeasurementApiStatus(any(), any())
-
-        // Actually invoke the compat code.
-        val actualResult = runBlocking {
-            managerCompat!!.getMeasurementApiStatus()
-        }
-
-        // Verify that the compat code was invoked correctly.
-        verify(measurementManager).getMeasurementApiStatus(any(), any())
-
-        // Verify that the request that the compat code makes to the platform is correct.
-        assertThat(actualResult == state)
+        val measurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersion)
+        callAndVerifyGetMeasurementApiStatus(
+            measurementManager,
+            /* state= */ MeasurementManager.MEASUREMENT_API_STATE_ENABLED,
+            /* expectedResult= */ MeasurementManager.MEASUREMENT_API_STATE_ENABLED)
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testMeasurementApiStatusUnknown() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5 or API 31/32 ext 9",
+            mValidAdServicesSdkExtVersion || mValidAdExtServicesSdkExtVersion)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
-        val measurementManager = mockMeasurementManager(mContext)
-        val managerCompat = obtain(mContext)
-        val answer = { args: InvocationOnMock ->
-            val receiver = args.getArgument<OutcomeReceiver<Int, Exception>>(1)
-            receiver.onResult(6 /* Greater than values returned in SdkExtensions.AD_SERVICES = 5 */)
-            null
-        }
-        doAnswer(answer).`when`(measurementManager).getMeasurementApiStatus(any(), any())
+        val measurementManager = mockMeasurementManager(mContext, mValidAdExtServicesSdkExtVersion)
 
-        // Actually invoke the compat code.
-        val actualResult = runBlocking {
-            managerCompat!!.getMeasurementApiStatus()
-        }
-
-        // Verify that the compat code was invoked correctly.
-        verify(measurementManager).getMeasurementApiStatus(any(), any())
-
-        // Verify that the request that the compat code makes to the platform is correct.
+        // Call with a value greater than values returned in SdkExtensions.AD_SERVICES = 5
         // Since the compat code does not know the returned state, it sets it to UNKNOWN.
-        assertThat(actualResult == 5)
+        callAndVerifyGetMeasurementApiStatus(
+            measurementManager,
+            /* state= */ 6,
+            /* expectedResult= */ 5)
     }
 
     @SdkSuppress(minSdkVersion = 30)
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     companion object {
 
         private val uri1: Uri = Uri.parse("www.abc.com")
@@ -420,13 +405,46 @@
 
         private lateinit var mContext: Context
 
-        private fun mockMeasurementManager(spyContext: Context): MeasurementManager {
+        private fun mockMeasurementManager(
+            spyContext: Context,
+            isExtServices: Boolean
+        ): MeasurementManager {
             val measurementManager = mock(MeasurementManager::class.java)
             `when`(spyContext.getSystemService(MeasurementManager::class.java))
                 .thenReturn(measurementManager)
+            // only mock the .get() method if using the extServices version
+            if (isExtServices) {
+                `when`(MeasurementManager.get(any()))
+                    .thenReturn(measurementManager)
+            }
             return measurementManager
         }
 
+        private fun callAndVerifyGetMeasurementApiStatus(
+            measurementManager: android.adservices.measurement.MeasurementManager,
+            state: Int,
+            expectedResult: Int
+        ) {
+            val managerCompat = obtain(mContext)
+            val answer = { args: InvocationOnMock ->
+                val receiver = args.getArgument<OutcomeReceiver<Int, Exception>>(1)
+                receiver.onResult(state)
+                null
+            }
+            doAnswer(answer).`when`(measurementManager).getMeasurementApiStatus(any(), any())
+
+            // Actually invoke the compat code.
+            val actualResult = runBlocking {
+                managerCompat!!.getMeasurementApiStatus()
+            }
+
+            // Verify that the compat code was invoked correctly.
+            verify(measurementManager).getMeasurementApiStatus(any(), any())
+
+            // Verify that the request that the compat code makes to the platform is correct.
+            assertThat(actualResult == expectedResult)
+        }
+
         private fun verifyDeletionRequest(request: android.adservices.measurement.DeletionRequest) {
             // Set up the request that we expect the compat code to invoke.
             val expectedRequest = android.adservices.measurement.DeletionRequest.Builder()
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/topics/TopicsManagerTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/topics/TopicsManagerTest.kt
index d79e94e..82a492b 100644
--- a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/topics/TopicsManagerTest.kt
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/topics/TopicsManagerTest.kt
@@ -20,15 +20,17 @@
 import android.adservices.topics.TopicsManager
 import android.content.Context
 import android.os.OutcomeReceiver
-import android.os.ext.SdkExtensions
-import androidx.annotation.RequiresExtension
+import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo
 import androidx.privacysandbox.ads.adservices.topics.TopicsManager.Companion.obtain
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.runBlocking
+import org.junit.After
 import org.junit.Assert
 import org.junit.Assume
 import org.junit.Before
@@ -42,6 +44,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.invocation.InvocationOnMock
+import org.mockito.quality.Strictness
 
 @SmallTest
 @SuppressWarnings("NewApi")
@@ -49,42 +52,60 @@
 @SdkSuppress(minSdkVersion = 30)
 class TopicsManagerTest {
 
+    private var mSession: StaticMockitoSession? = null
+    private val mValidAdServicesSdkExt4Version = AdServicesInfo.adServicesVersion() >= 4
+    private val mValidAdServicesSdkExt5Version = AdServicesInfo.adServicesVersion() >= 5
+    private val mValidAdExtServicesSdkExtVersion = AdServicesInfo.extServicesVersion() >= 9
+
     @Before
     fun setUp() {
         mContext = spy(ApplicationProvider.getApplicationContext<Context>())
+
+        if (mValidAdExtServicesSdkExtVersion) {
+            // setup a mockitoSession to return the mocked manager
+            // when the static method .get() is called
+            mSession = ExtendedMockito.mockitoSession()
+                .mockStatic(android.adservices.topics.TopicsManager::class.java)
+                .strictness(Strictness.LENIENT)
+                .startMocking()
+        }
+    }
+
+    @After
+    fun tearDown() {
+        mSession?.finishMocking()
     }
 
     @Test
     @SdkSuppress(maxSdkVersion = 33, minSdkVersion = 30)
     fun testTopicsOlderVersions() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
-
-        Assume.assumeTrue("maxSdkVersion = API 33 ext 3", sdkExtVersion < 4)
+        Assume.assumeTrue("maxSdkVersion = API 33 ext 3", !mValidAdServicesSdkExt4Version)
+        Assume.assumeTrue("maxSdkVersion = API 31/32 ext 8", !mValidAdExtServicesSdkExtVersion)
         assertThat(obtain(mContext)).isEqualTo(null)
     }
 
     @Test
-    @SuppressWarnings("NewApi")
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
     fun testTopicsAsync() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 4 or API 31/32 ext 9",
+            mValidAdServicesSdkExt4Version || mValidAdExtServicesSdkExtVersion)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
-        val topicsManager = mockTopicsManager(mContext)
+        val topicsManager = mockTopicsManager(mContext, mValidAdExtServicesSdkExtVersion)
         setupTopicsResponse(topicsManager)
         val managerCompat = obtain(mContext)
 
         // Actually invoke the compat code.
         val result = runBlocking {
-            val request =
-                GetTopicsRequest.Builder().setAdsSdkName(mSdkName).setShouldRecordObservation(true)
-                    .build()
+            val request = GetTopicsRequest.Builder()
+                .setAdsSdkName(mSdkName)
+                .setShouldRecordObservation(true)
+                .build()
 
             managerCompat!!.getTopics(request)
         }
 
         // Verify that the compat code was invoked correctly.
-        val captor = ArgumentCaptor.forClass(android.adservices.topics.GetTopicsRequest::class.java)
+        val captor = ArgumentCaptor
+            .forClass(android.adservices.topics.GetTopicsRequest::class.java)
         verify(topicsManager).getTopics(captor.capture(), any(), any())
 
         // Verify that the request that the compat code makes to the platform is correct.
@@ -95,26 +116,28 @@
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testTopicsAsyncPreviewSupported() {
-        val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5 or API 31/32 ext 9",
+            mValidAdServicesSdkExt5Version || mValidAdExtServicesSdkExtVersion)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
-        val topicsManager = mockTopicsManager(mContext)
+        val topicsManager = mockTopicsManager(mContext, mValidAdExtServicesSdkExtVersion)
         setupTopicsResponse(topicsManager)
         val managerCompat = obtain(mContext)
 
         // Actually invoke the compat Preview API code.
         val result = runBlocking {
             val request =
-                GetTopicsRequest.Builder().setAdsSdkName(mSdkName).setShouldRecordObservation(false)
+                GetTopicsRequest.Builder()
+                    .setAdsSdkName(mSdkName)
+                    .setShouldRecordObservation(false)
                     .build()
 
             managerCompat!!.getTopics(request)
         }
 
         // Verify that the compat code was invoked correctly.
-        val captor = ArgumentCaptor.forClass(android.adservices.topics.GetTopicsRequest::class.java)
+        val captor = ArgumentCaptor
+            .forClass(android.adservices.topics.GetTopicsRequest::class.java)
         verify(topicsManager).getTopics(captor.capture(), any(), any())
 
         // Verify that the request that the compat code makes to the platform is correct.
@@ -125,14 +148,17 @@
     }
 
     @SdkSuppress(minSdkVersion = 30)
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
     companion object {
         private lateinit var mContext: Context
         private val mSdkName: String = "sdk1"
 
-        private fun mockTopicsManager(spyContext: Context): TopicsManager {
+        private fun mockTopicsManager(spyContext: Context, isExtServices: Boolean): TopicsManager {
             val topicsManager = mock(TopicsManager::class.java)
             `when`(spyContext.getSystemService(TopicsManager::class.java)).thenReturn(topicsManager)
+            // only mock the .get() method if using extServices version
+            if (isExtServices) {
+                `when`(TopicsManager.get(any())).thenReturn(topicsManager)
+            }
             return topicsManager
         }
 
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adid/AdIdManager.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adid/AdIdManager.kt
index 17431a8..632da1b 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adid/AdIdManager.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adid/AdIdManager.kt
@@ -20,13 +20,8 @@
 import android.annotation.SuppressLint
 import android.content.Context
 import android.os.LimitExceededException
-import android.os.ext.SdkExtensions
-import androidx.annotation.DoNotInline
-import androidx.annotation.RequiresExtension
 import androidx.annotation.RequiresPermission
-import androidx.core.os.asOutcomeReceiver
 import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo
-import kotlinx.coroutines.suspendCancellableCoroutine
 
 /**
  * AdId Manager provides APIs for app and ad-SDKs to access advertising ID. The advertising ID is a
@@ -45,38 +40,6 @@
     @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_AD_ID)
     abstract suspend fun getAdId(): AdId
 
-    @SuppressLint("ClassVerificationFailure", "NewApi")
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
-    private class Api33Ext4Impl(
-        private val mAdIdManager: android.adservices.adid.AdIdManager
-    ) : AdIdManager() {
-        constructor(context: Context) : this(
-            context.getSystemService<android.adservices.adid.AdIdManager>(
-                android.adservices.adid.AdIdManager::class.java
-            )
-        )
-
-        @DoNotInline
-        @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_AD_ID)
-        override suspend fun getAdId(): AdId {
-            return convertResponse(getAdIdAsyncInternal())
-        }
-
-        @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_AD_ID)
-        private suspend fun
-            getAdIdAsyncInternal(): android.adservices.adid.AdId = suspendCancellableCoroutine {
-                continuation ->
-            mAdIdManager.getAdId(
-                Runnable::run,
-                continuation.asOutcomeReceiver()
-            )
-        }
-
-        private fun convertResponse(response: android.adservices.adid.AdId): AdId {
-            return AdId(response.adId, response.isLimitAdTrackingEnabled)
-        }
-    }
-
     companion object {
         /**
          *  Creates [AdIdManager].
@@ -87,10 +50,11 @@
         @JvmStatic
         @SuppressLint("NewApi", "ClassVerificationFailure")
         fun obtain(context: Context): AdIdManager? {
-            return if (AdServicesInfo.version() >= 4) {
-                Api33Ext4Impl(context)
+            return if (AdServicesInfo.adServicesVersion() >= 4) {
+                AdIdManagerApi33Ext4Impl(context)
+            } else if (AdServicesInfo.extServicesVersion() >= 9) {
+                AdIdManagerApi31Ext9Impl(context)
             } else {
-                // TODO(b/261770989): Extend this to older versions.
                 null
             }
         }
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adid/AdIdManagerApi31Ext9Impl.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adid/AdIdManagerApi31Ext9Impl.kt
new file mode 100644
index 0000000..28d3d76
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adid/AdIdManagerApi31Ext9Impl.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.
+ */
+
+package androidx.privacysandbox.ads.adservices.adid
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.Build
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressLint("NewApi", "ClassVerificationFailure")
+@RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+class AdIdManagerApi31Ext9Impl(context: Context) : AdIdManagerImplCommon(
+    android.adservices.adid.AdIdManager.get(context))
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adid/AdIdManagerApi33Ext4Impl.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adid/AdIdManagerApi33Ext4Impl.kt
new file mode 100644
index 0000000..a043fba
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adid/AdIdManagerApi33Ext4Impl.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.privacysandbox.ads.adservices.adid
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.ext.SdkExtensions
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressLint("NewApi", "ClassVerificationFailure")
+@RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+class AdIdManagerApi33Ext4Impl(context: Context) : AdIdManagerImplCommon(
+    context.getSystemService(
+        android.adservices.adid.AdIdManager::class.java))
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adid/AdIdManagerImplCommon.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adid/AdIdManagerImplCommon.kt
new file mode 100644
index 0000000..4ba59b2
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adid/AdIdManagerImplCommon.kt
@@ -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.privacysandbox.ads.adservices.adid
+
+import android.adservices.common.AdServicesPermissions
+import android.annotation.SuppressLint
+import android.os.Build
+import android.os.ext.SdkExtensions
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RequiresPermission
+import androidx.annotation.RestrictTo
+import androidx.core.os.asOutcomeReceiver
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressLint("ClassVerificationFailure", "NewApi")
+@RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+@RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+open class AdIdManagerImplCommon(
+    private val mAdIdManager: android.adservices.adid.AdIdManager
+) : AdIdManager() {
+
+    @DoNotInline
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_AD_ID)
+    override suspend fun getAdId(): AdId {
+        return convertResponse(getAdIdAsyncInternal())
+    }
+
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_AD_ID)
+    private suspend fun
+        getAdIdAsyncInternal(): android.adservices.adid.AdId = suspendCancellableCoroutine {
+            continuation ->
+        mAdIdManager.getAdId(
+            Runnable::run,
+            continuation.asOutcomeReceiver()
+        )
+    }
+
+    private fun convertResponse(response: android.adservices.adid.AdId): AdId {
+        return AdId(response.adId, response.isLimitAdTrackingEnabled)
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManager.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManager.kt
index 7e280c4..311bc63 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManager.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManager.kt
@@ -21,16 +21,9 @@
 import android.content.Context
 import android.os.LimitExceededException
 import android.os.TransactionTooLargeException
-import android.os.ext.SdkExtensions
-import androidx.annotation.DoNotInline
-import androidx.annotation.RequiresExtension
 import androidx.annotation.RequiresPermission
-import androidx.core.os.asOutcomeReceiver
-import androidx.privacysandbox.ads.adservices.common.AdSelectionSignals
-import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
 import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo
 import java.util.concurrent.TimeoutException
-import kotlinx.coroutines.suspendCancellableCoroutine
 
 /**
  * AdSelection Manager provides APIs for app and ad-SDKs to run ad selection processes as well
@@ -75,109 +68,6 @@
     @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
     abstract suspend fun reportImpression(reportImpressionRequest: ReportImpressionRequest)
 
-    @SuppressLint("NewApi", "ClassVerificationFailure")
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
-    private class Api33Ext4Impl(
-        private val mAdSelectionManager: android.adservices.adselection.AdSelectionManager
-    ) : AdSelectionManager() {
-        constructor(context: Context) : this(
-            context.getSystemService<android.adservices.adselection.AdSelectionManager>(
-                android.adservices.adselection.AdSelectionManager::class.java
-            )
-        )
-
-        @DoNotInline
-        @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
-        override suspend fun selectAds(adSelectionConfig: AdSelectionConfig): AdSelectionOutcome {
-            return convertResponse(selectAdsInternal(convertAdSelectionConfig(adSelectionConfig)))
-        }
-
-        @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
-        private suspend fun selectAdsInternal(
-            adSelectionConfig: android.adservices.adselection.AdSelectionConfig
-        ): android.adservices.adselection.AdSelectionOutcome = suspendCancellableCoroutine { cont
-            ->
-            mAdSelectionManager.selectAds(
-                adSelectionConfig,
-                Runnable::run,
-                cont.asOutcomeReceiver()
-            )
-        }
-
-        private fun convertAdSelectionConfig(
-            request: AdSelectionConfig
-        ): android.adservices.adselection.AdSelectionConfig {
-            return android.adservices.adselection.AdSelectionConfig.Builder()
-                .setAdSelectionSignals(convertAdSelectionSignals(request.adSelectionSignals))
-                .setCustomAudienceBuyers(convertBuyers(request.customAudienceBuyers))
-                .setDecisionLogicUri(request.decisionLogicUri)
-                .setSeller(android.adservices.common.AdTechIdentifier.fromString(
-                    request.seller.identifier))
-                .setPerBuyerSignals(convertPerBuyerSignals(request.perBuyerSignals))
-                .setSellerSignals(convertAdSelectionSignals(request.sellerSignals))
-                .setTrustedScoringSignalsUri(request.trustedScoringSignalsUri)
-                .build()
-        }
-
-        private fun convertAdSelectionSignals(
-            request: AdSelectionSignals
-        ): android.adservices.common.AdSelectionSignals {
-            return android.adservices.common.AdSelectionSignals.fromString(request.signals)
-        }
-
-        private fun convertBuyers(
-            buyers: List<AdTechIdentifier>
-        ): MutableList<android.adservices.common.AdTechIdentifier> {
-            var ids = mutableListOf<android.adservices.common.AdTechIdentifier>()
-            for (buyer in buyers) {
-                ids.add(android.adservices.common.AdTechIdentifier.fromString(buyer.identifier))
-            }
-            return ids
-        }
-
-        private fun convertPerBuyerSignals(
-            request: Map<AdTechIdentifier, AdSelectionSignals>
-        ): Map<android.adservices.common.AdTechIdentifier,
-            android.adservices.common.AdSelectionSignals?> {
-            var map = HashMap<android.adservices.common.AdTechIdentifier,
-                android.adservices.common.AdSelectionSignals?>()
-            for (key in request.keys) {
-                val id = android.adservices.common.AdTechIdentifier.fromString(key.identifier)
-                val value = if (request[key] != null) convertAdSelectionSignals(request[key]!!)
-                    else null
-                map[id] = value
-            }
-            return map
-        }
-
-        private fun convertResponse(
-            response: android.adservices.adselection.AdSelectionOutcome
-        ): AdSelectionOutcome {
-            return AdSelectionOutcome(response.adSelectionId, response.renderUri)
-        }
-
-        @DoNotInline
-        @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
-        override suspend fun reportImpression(reportImpressionRequest: ReportImpressionRequest) {
-            suspendCancellableCoroutine<Any> { continuation ->
-                mAdSelectionManager.reportImpression(
-                    convertReportImpressionRequest(reportImpressionRequest),
-                    Runnable::run,
-                    continuation.asOutcomeReceiver()
-                )
-            }
-        }
-
-        private fun convertReportImpressionRequest(
-            request: ReportImpressionRequest
-        ): android.adservices.adselection.ReportImpressionRequest {
-            return android.adservices.adselection.ReportImpressionRequest(
-                request.adSelectionId,
-                convertAdSelectionConfig(request.adSelectionConfig)
-            )
-        }
-    }
-
     companion object {
         /**
          *  Creates [AdSelectionManager].
@@ -188,8 +78,10 @@
         @JvmStatic
         @SuppressLint("NewApi", "ClassVerificationFailure")
         fun obtain(context: Context): AdSelectionManager? {
-            return if (AdServicesInfo.version() >= 4) {
-                Api33Ext4Impl(context)
+            return if (AdServicesInfo.adServicesVersion() >= 4) {
+                AdSelectionManagerApi33Ext4Impl(context)
+            } else if (AdServicesInfo.extServicesVersion() >= 9) {
+                AdSelectionManagerApi31Ext9Impl(context)
             } else {
                 null
             }
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManagerApi31Ext9Impl.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManagerApi31Ext9Impl.kt
new file mode 100644
index 0000000..d968dd2
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManagerApi31Ext9Impl.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.
+ */
+
+package androidx.privacysandbox.ads.adservices.adselection
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.Build
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressLint("NewApi", "ClassVerificationFailure")
+@RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+class AdSelectionManagerApi31Ext9Impl(context: Context) : AdSelectionManagerImplCommon(
+    android.adservices.adselection.AdSelectionManager.get(context))
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManagerApi33Ext4Impl.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManagerApi33Ext4Impl.kt
new file mode 100644
index 0000000..b3cdd9c
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManagerApi33Ext4Impl.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.privacysandbox.ads.adservices.adselection
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.ext.SdkExtensions
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressLint("NewApi", "ClassVerificationFailure")
+@RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+class AdSelectionManagerApi33Ext4Impl(context: Context) : AdSelectionManagerImplCommon(
+    context.getSystemService(
+        android.adservices.adselection.AdSelectionManager::class.java))
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManagerImplCommon.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManagerImplCommon.kt
new file mode 100644
index 0000000..e780975
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/AdSelectionManagerImplCommon.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.privacysandbox.ads.adservices.adselection
+
+import android.adservices.common.AdServicesPermissions
+import android.annotation.SuppressLint
+import android.os.Build
+import android.os.ext.SdkExtensions
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RequiresPermission
+import androidx.annotation.RestrictTo
+import androidx.core.os.asOutcomeReceiver
+import androidx.privacysandbox.ads.adservices.common.AdSelectionSignals
+import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressLint("NewApi", "ClassVerificationFailure")
+@RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+@RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+open class AdSelectionManagerImplCommon(
+    protected val mAdSelectionManager: android.adservices.adselection.AdSelectionManager
+    ) : AdSelectionManager() {
+
+    @DoNotInline
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+    override suspend fun selectAds(adSelectionConfig: AdSelectionConfig): AdSelectionOutcome {
+        return convertResponse(selectAdsInternal(convertAdSelectionConfig(adSelectionConfig)))
+    }
+
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+    private suspend fun selectAdsInternal(
+        adSelectionConfig: android.adservices.adselection.AdSelectionConfig
+    ): android.adservices.adselection.AdSelectionOutcome = suspendCancellableCoroutine { cont
+        ->
+        mAdSelectionManager.selectAds(
+            adSelectionConfig,
+            Runnable::run,
+            cont.asOutcomeReceiver()
+        )
+    }
+
+    private fun convertAdSelectionConfig(
+        request: AdSelectionConfig
+    ): android.adservices.adselection.AdSelectionConfig {
+        return android.adservices.adselection.AdSelectionConfig.Builder()
+            .setAdSelectionSignals(convertAdSelectionSignals(request.adSelectionSignals))
+            .setCustomAudienceBuyers(convertBuyers(request.customAudienceBuyers))
+            .setDecisionLogicUri(request.decisionLogicUri)
+            .setSeller(android.adservices.common.AdTechIdentifier.fromString(
+                request.seller.identifier))
+            .setPerBuyerSignals(convertPerBuyerSignals(request.perBuyerSignals))
+            .setSellerSignals(convertAdSelectionSignals(request.sellerSignals))
+            .setTrustedScoringSignalsUri(request.trustedScoringSignalsUri)
+            .build()
+    }
+
+    private fun convertAdSelectionSignals(
+        request: AdSelectionSignals
+    ): android.adservices.common.AdSelectionSignals {
+        return android.adservices.common.AdSelectionSignals.fromString(request.signals)
+    }
+
+    private fun convertBuyers(
+        buyers: List<AdTechIdentifier>
+    ): MutableList<android.adservices.common.AdTechIdentifier> {
+        val ids = mutableListOf<android.adservices.common.AdTechIdentifier>()
+        for (buyer in buyers) {
+            ids.add(android.adservices.common.AdTechIdentifier.fromString(buyer.identifier))
+        }
+        return ids
+    }
+
+    private fun convertPerBuyerSignals(
+        request: Map<AdTechIdentifier, AdSelectionSignals>
+    ): Map<android.adservices.common.AdTechIdentifier,
+        android.adservices.common.AdSelectionSignals?> {
+        val map = HashMap<android.adservices.common.AdTechIdentifier,
+            android.adservices.common.AdSelectionSignals?>()
+        for (key in request.keys) {
+            val id = android.adservices.common.AdTechIdentifier.fromString(key.identifier)
+            val value = if (request[key] != null) convertAdSelectionSignals(request[key]!!)
+            else null
+            map[id] = value
+        }
+        return map
+    }
+
+    private fun convertResponse(
+        response: android.adservices.adselection.AdSelectionOutcome
+    ): AdSelectionOutcome {
+        return AdSelectionOutcome(response.adSelectionId, response.renderUri)
+    }
+
+    @DoNotInline
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+    override suspend fun reportImpression(reportImpressionRequest: ReportImpressionRequest) {
+        suspendCancellableCoroutine<Any> { continuation ->
+            mAdSelectionManager.reportImpression(
+                convertReportImpressionRequest(reportImpressionRequest),
+                Runnable::run,
+                continuation.asOutcomeReceiver()
+            )
+        }
+    }
+
+    private fun convertReportImpressionRequest(
+        request: ReportImpressionRequest
+    ): android.adservices.adselection.ReportImpressionRequest {
+        return android.adservices.adselection.ReportImpressionRequest(
+            request.adSelectionId,
+            convertAdSelectionConfig(request.adSelectionConfig)
+        )
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/appsetid/AppSetIdManager.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/appsetid/AppSetIdManager.kt
index d780956..88d57f1 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/appsetid/AppSetIdManager.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/appsetid/AppSetIdManager.kt
@@ -19,12 +19,7 @@
 import android.annotation.SuppressLint
 import android.content.Context
 import android.os.LimitExceededException
-import android.os.ext.SdkExtensions
-import androidx.annotation.DoNotInline
-import androidx.annotation.RequiresExtension
-import androidx.core.os.asOutcomeReceiver
 import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo
-import kotlinx.coroutines.suspendCancellableCoroutine
 
 /**
  * AppSetIdManager provides APIs for app and ad-SDKs to access appSetId for non-monetizing purpose.
@@ -39,39 +34,6 @@
      */
     abstract suspend fun getAppSetId(): AppSetId
 
-    @SuppressLint("ClassVerificationFailure", "NewApi")
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
-    private class Api33Ext4Impl(
-        private val mAppSetIdManager: android.adservices.appsetid.AppSetIdManager
-    ) : AppSetIdManager() {
-        constructor(context: Context) : this(
-            context.getSystemService<android.adservices.appsetid.AppSetIdManager>(
-                android.adservices.appsetid.AppSetIdManager::class.java
-            )
-        )
-
-        @DoNotInline
-        override suspend fun getAppSetId(): AppSetId {
-            return convertResponse(getAppSetIdAsyncInternal())
-        }
-
-        private suspend fun getAppSetIdAsyncInternal(): android.adservices.appsetid.AppSetId =
-            suspendCancellableCoroutine {
-                    continuation ->
-                mAppSetIdManager.getAppSetId(
-                    Runnable::run,
-                    continuation.asOutcomeReceiver()
-                )
-            }
-
-        private fun convertResponse(response: android.adservices.appsetid.AppSetId): AppSetId {
-            if (response.scope == android.adservices.appsetid.AppSetId.SCOPE_APP) {
-                return AppSetId(response.id, AppSetId.SCOPE_APP)
-            }
-            return AppSetId(response.id, AppSetId.SCOPE_DEVELOPER)
-        }
-    }
-
     companion object {
 
         /**
@@ -83,10 +45,11 @@
         @JvmStatic
         @SuppressLint("NewApi", "ClassVerificationFailure")
         fun obtain(context: Context): AppSetIdManager? {
-            return if (AdServicesInfo.version() >= 4) {
-                Api33Ext4Impl(context)
+            return if (AdServicesInfo.adServicesVersion() >= 4) {
+                AppSetIdManagerApi33Ext4Impl(context)
+            } else if (AdServicesInfo.extServicesVersion() >= 9) {
+                AppSetIdManagerApi31Ext9Impl(context)
             } else {
-                // TODO(b/261770989): Extend this to older versions.
                 null
             }
         }
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/appsetid/AppSetIdManagerApi31Ext9Impl.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/appsetid/AppSetIdManagerApi31Ext9Impl.kt
new file mode 100644
index 0000000..390e785
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/appsetid/AppSetIdManagerApi31Ext9Impl.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.
+ */
+
+package androidx.privacysandbox.ads.adservices.appsetid
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.Build
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressLint("NewApi", "ClassVerificationFailure")
+@RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+class AppSetIdManagerApi31Ext9Impl(context: Context) : AppSetIdManagerImplCommon(
+    android.adservices.appsetid.AppSetIdManager.get(context))
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/appsetid/AppSetIdManagerApi33Ext4Impl.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/appsetid/AppSetIdManagerApi33Ext4Impl.kt
new file mode 100644
index 0000000..84af87a
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/appsetid/AppSetIdManagerApi33Ext4Impl.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.privacysandbox.ads.adservices.appsetid
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.ext.SdkExtensions
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressLint("NewApi", "ClassVerificationFailure")
+@RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+class AppSetIdManagerApi33Ext4Impl(context: Context) : AppSetIdManagerImplCommon(
+    context.getSystemService(
+        android.adservices.appsetid.AppSetIdManager::class.java))
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/appsetid/AppSetIdManagerImplCommon.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/appsetid/AppSetIdManagerImplCommon.kt
new file mode 100644
index 0000000..720370d
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/appsetid/AppSetIdManagerImplCommon.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.privacysandbox.ads.adservices.appsetid
+
+import android.annotation.SuppressLint
+import android.os.Build
+import android.os.ext.SdkExtensions
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+import androidx.core.os.asOutcomeReceiver
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressLint("ClassVerificationFailure", "NewApi")
+@RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+@RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+open class AppSetIdManagerImplCommon(
+    private val mAppSetIdManager: android.adservices.appsetid.AppSetIdManager
+) : AppSetIdManager() {
+
+    @DoNotInline
+    override suspend fun getAppSetId(): AppSetId {
+        return convertResponse(getAppSetIdAsyncInternal())
+    }
+
+    private suspend fun getAppSetIdAsyncInternal(): android.adservices.appsetid.AppSetId =
+        suspendCancellableCoroutine {
+                continuation ->
+            mAppSetIdManager.getAppSetId(
+                Runnable::run,
+                continuation.asOutcomeReceiver()
+            )
+        }
+
+    private fun convertResponse(response: android.adservices.appsetid.AppSetId): AppSetId {
+        if (response.scope == android.adservices.appsetid.AppSetId.SCOPE_APP) {
+            return AppSetId(response.id, AppSetId.SCOPE_APP)
+        }
+        return AppSetId(response.id, AppSetId.SCOPE_DEVELOPER)
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManager.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManager.kt
index 981aeaa..a9b91d7 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManager.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManager.kt
@@ -20,16 +20,8 @@
 import android.annotation.SuppressLint
 import android.content.Context
 import android.os.LimitExceededException
-import android.os.ext.SdkExtensions
-import androidx.annotation.DoNotInline
-import androidx.annotation.RequiresExtension
 import androidx.annotation.RequiresPermission
-import androidx.core.os.asOutcomeReceiver
-import androidx.privacysandbox.ads.adservices.common.AdData
-import androidx.privacysandbox.ads.adservices.common.AdSelectionSignals
-import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
 import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo
-import kotlinx.coroutines.suspendCancellableCoroutine
 
 /**
  * This class provides APIs for app and ad-SDKs to join / leave custom audiences.
@@ -94,111 +86,6 @@
     @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
     abstract suspend fun leaveCustomAudience(request: LeaveCustomAudienceRequest)
 
-    @SuppressLint("ClassVerificationFailure", "NewApi")
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
-    private class Api33Ext4Impl(
-        private val customAudienceManager: android.adservices.customaudience.CustomAudienceManager
-        ) : CustomAudienceManager() {
-        constructor(context: Context) : this(
-            context.getSystemService<android.adservices.customaudience.CustomAudienceManager>(
-                android.adservices.customaudience.CustomAudienceManager::class.java
-            )
-        )
-
-        @DoNotInline
-        @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
-        override suspend fun joinCustomAudience(request: JoinCustomAudienceRequest) {
-            suspendCancellableCoroutine { continuation ->
-                customAudienceManager.joinCustomAudience(
-                    convertJoinRequest(request),
-                    Runnable::run,
-                    continuation.asOutcomeReceiver()
-                )
-            }
-        }
-
-        @DoNotInline
-        @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
-        override suspend fun leaveCustomAudience(request: LeaveCustomAudienceRequest) {
-            suspendCancellableCoroutine { continuation ->
-                customAudienceManager.leaveCustomAudience(
-                    convertLeaveRequest(request),
-                    Runnable::run,
-                    continuation.asOutcomeReceiver()
-                )
-            }
-        }
-
-        private fun convertJoinRequest(
-            request: JoinCustomAudienceRequest
-        ): android.adservices.customaudience.JoinCustomAudienceRequest {
-            return android.adservices.customaudience.JoinCustomAudienceRequest.Builder()
-                .setCustomAudience(convertCustomAudience(request.customAudience))
-                .build()
-        }
-
-        private fun convertLeaveRequest(
-            request: LeaveCustomAudienceRequest
-        ): android.adservices.customaudience.LeaveCustomAudienceRequest {
-            return android.adservices.customaudience.LeaveCustomAudienceRequest.Builder()
-                .setBuyer(convertAdTechIdentifier(request.buyer))
-                .setName(request.name)
-                .build()
-        }
-
-        private fun convertCustomAudience(
-            request: CustomAudience
-        ): android.adservices.customaudience.CustomAudience {
-            return android.adservices.customaudience.CustomAudience.Builder()
-                .setActivationTime(request.activationTime)
-                .setAds(convertAdData(request.ads))
-                .setBiddingLogicUri(request.biddingLogicUri)
-                .setBuyer(convertAdTechIdentifier(request.buyer))
-                .setDailyUpdateUri(request.dailyUpdateUri)
-                .setExpirationTime(request.expirationTime)
-                .setName(request.name)
-                .setTrustedBiddingData(convertTrustedSignals(request.trustedBiddingSignals))
-                .setUserBiddingSignals(convertBiddingSignals(request.userBiddingSignals))
-                .build()
-        }
-
-        private fun convertAdData(
-            input: List<AdData>
-        ): List<android.adservices.common.AdData> {
-            val result = mutableListOf<android.adservices.common.AdData>()
-            for (ad in input) {
-                result.add(android.adservices.common.AdData.Builder()
-                    .setMetadata(ad.metadata)
-                    .setRenderUri(ad.renderUri)
-                    .build())
-            }
-            return result
-        }
-
-        private fun convertAdTechIdentifier(
-            input: AdTechIdentifier
-        ): android.adservices.common.AdTechIdentifier {
-            return android.adservices.common.AdTechIdentifier.fromString(input.identifier)
-        }
-
-        private fun convertTrustedSignals(
-            input: TrustedBiddingData?
-        ): android.adservices.customaudience.TrustedBiddingData? {
-            if (input == null) return null
-            return android.adservices.customaudience.TrustedBiddingData.Builder()
-                .setTrustedBiddingKeys(input.trustedBiddingKeys)
-                .setTrustedBiddingUri(input.trustedBiddingUri)
-                .build()
-        }
-
-        private fun convertBiddingSignals(
-            input: AdSelectionSignals?
-        ): android.adservices.common.AdSelectionSignals? {
-            if (input == null) return null
-            return android.adservices.common.AdSelectionSignals.fromString(input.signals)
-        }
-    }
-
     companion object {
         /**
          *  Creates [CustomAudienceManager].
@@ -209,8 +96,10 @@
         @JvmStatic
         @SuppressLint("NewApi", "ClassVerificationFailure")
         fun obtain(context: Context): CustomAudienceManager? {
-            return if (AdServicesInfo.version() >= 4) {
-                Api33Ext4Impl(context)
+            return if (AdServicesInfo.adServicesVersion() >= 4) {
+                CustomAudienceManagerApi33Ext4Impl(context)
+            } else if (AdServicesInfo.extServicesVersion() >= 9) {
+                CustomAudienceManagerApi31Ext9Impl(context)
             } else {
                 null
             }
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManagerApi31Ext9Impl.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManagerApi31Ext9Impl.kt
new file mode 100644
index 0000000..1e294ce
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManagerApi31Ext9Impl.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.
+ */
+
+package androidx.privacysandbox.ads.adservices.customaudience
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.Build
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressLint("NewApi", "ClassVerificationFailure")
+@RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+class CustomAudienceManagerApi31Ext9Impl(context: Context) : CustomAudienceManagerImplCommon(
+    android.adservices.customaudience.CustomAudienceManager.get(context))
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManagerApi33Ext4Impl.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManagerApi33Ext4Impl.kt
new file mode 100644
index 0000000..fd658d7
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManagerApi33Ext4Impl.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.
+ */
+
+package androidx.privacysandbox.ads.adservices.customaudience
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.ext.SdkExtensions
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressLint("NewApi", "ClassVerificationFailure")
+@RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+class CustomAudienceManagerApi33Ext4Impl(context: Context) : CustomAudienceManagerImplCommon(
+    context.getSystemService(android.adservices.customaudience.CustomAudienceManager::class.java))
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManagerImplCommon.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManagerImplCommon.kt
new file mode 100644
index 0000000..44f09a8
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/customaudience/CustomAudienceManagerImplCommon.kt
@@ -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.privacysandbox.ads.adservices.customaudience
+
+import android.adservices.common.AdServicesPermissions
+import android.annotation.SuppressLint
+import android.os.Build
+import android.os.ext.SdkExtensions
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RequiresPermission
+import androidx.annotation.RestrictTo
+import androidx.core.os.asOutcomeReceiver
+import androidx.privacysandbox.ads.adservices.common.AdData
+import androidx.privacysandbox.ads.adservices.common.AdSelectionSignals
+import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressLint("NewApi", "ClassVerificationFailure")
+@RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+@RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+open class CustomAudienceManagerImplCommon(
+    protected val customAudienceManager: android.adservices.customaudience.CustomAudienceManager
+    ) : CustomAudienceManager() {
+    @DoNotInline
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+    override suspend fun joinCustomAudience(request: JoinCustomAudienceRequest) {
+        suspendCancellableCoroutine { continuation ->
+            customAudienceManager.joinCustomAudience(
+                convertJoinRequest(request),
+                Runnable::run,
+                continuation.asOutcomeReceiver()
+            )
+        }
+    }
+
+    @DoNotInline
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_CUSTOM_AUDIENCE)
+    override suspend fun leaveCustomAudience(request: LeaveCustomAudienceRequest) {
+        suspendCancellableCoroutine { continuation ->
+            customAudienceManager.leaveCustomAudience(
+                convertLeaveRequest(request),
+                Runnable::run,
+                continuation.asOutcomeReceiver()
+            )
+        }
+    }
+
+    private fun convertJoinRequest(
+        request: JoinCustomAudienceRequest
+    ): android.adservices.customaudience.JoinCustomAudienceRequest {
+        return android.adservices.customaudience.JoinCustomAudienceRequest.Builder()
+            .setCustomAudience(convertCustomAudience(request.customAudience))
+            .build()
+    }
+
+    private fun convertLeaveRequest(
+        request: LeaveCustomAudienceRequest
+    ): android.adservices.customaudience.LeaveCustomAudienceRequest {
+        return android.adservices.customaudience.LeaveCustomAudienceRequest.Builder()
+            .setBuyer(convertAdTechIdentifier(request.buyer))
+            .setName(request.name)
+            .build()
+    }
+
+    private fun convertCustomAudience(
+        request: CustomAudience
+    ): android.adservices.customaudience.CustomAudience {
+        return android.adservices.customaudience.CustomAudience.Builder()
+            .setActivationTime(request.activationTime)
+            .setAds(convertAdData(request.ads))
+            .setBiddingLogicUri(request.biddingLogicUri)
+            .setBuyer(convertAdTechIdentifier(request.buyer))
+            .setDailyUpdateUri(request.dailyUpdateUri)
+            .setExpirationTime(request.expirationTime)
+            .setName(request.name)
+            .setTrustedBiddingData(convertTrustedSignals(request.trustedBiddingSignals))
+            .setUserBiddingSignals(convertBiddingSignals(request.userBiddingSignals))
+            .build()
+    }
+
+    private fun convertAdData(
+        input: List<AdData>
+    ): List<android.adservices.common.AdData> {
+        val result = mutableListOf<android.adservices.common.AdData>()
+        for (ad in input) {
+            result.add(android.adservices.common.AdData.Builder()
+                .setMetadata(ad.metadata)
+                .setRenderUri(ad.renderUri)
+                .build())
+        }
+        return result
+    }
+
+    private fun convertAdTechIdentifier(
+        input: AdTechIdentifier
+    ): android.adservices.common.AdTechIdentifier {
+        return android.adservices.common.AdTechIdentifier.fromString(input.identifier)
+    }
+
+    private fun convertTrustedSignals(
+        input: TrustedBiddingData?
+    ): android.adservices.customaudience.TrustedBiddingData? {
+        if (input == null) return null
+        return android.adservices.customaudience.TrustedBiddingData.Builder()
+            .setTrustedBiddingKeys(input.trustedBiddingKeys)
+            .setTrustedBiddingUri(input.trustedBiddingUri)
+            .build()
+    }
+
+    private fun convertBiddingSignals(
+        input: AdSelectionSignals?
+    ): android.adservices.common.AdSelectionSignals? {
+        if (input == null) return null
+        return android.adservices.common.AdSelectionSignals.fromString(input.signals)
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/internal/AdServicesInfo.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/internal/AdServicesInfo.kt
index 9b36692..6f8b056 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/internal/AdServicesInfo.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/internal/AdServicesInfo.kt
@@ -21,24 +21,38 @@
 import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 
-/**
- * Temporary replacement for BuildCompat.AD_SERVICES_EXTENSION_INT.
- * TODO(b/261755947) Replace with AD_SERVICES_EXTENSION_INT after new core library release
- */
 internal object AdServicesInfo {
 
-    fun version(): Int {
-        return if (Build.VERSION.SDK_INT >= 30) {
+    fun adServicesVersion(): Int {
+        return if (Build.VERSION.SDK_INT >= 33) {
             Extensions30Impl.getAdServicesVersion()
         } else {
             0
         }
     }
 
+    fun extServicesVersion(): Int {
+        return if (Build.VERSION.SDK_INT == 31 || Build.VERSION.SDK_INT == 32) {
+            Extensions30ExtImpl.getAdExtServicesVersion()
+        } else {
+            0
+        }
+    }
+
     @RequiresApi(30)
     private object Extensions30Impl {
         @DoNotInline
         fun getAdServicesVersion() =
             SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
     }
+
+    @RequiresApi(30)
+    private object Extensions30ExtImpl {
+        // For ExtServices, there is no AD_SERVICES extension version, so we need to check
+        // for the build version. Use S for now, but this can be changed to R when we add
+        // support for R later.
+        @DoNotInline
+        fun getAdExtServicesVersion() =
+            SdkExtensions.getExtensionVersion(Build.VERSION_CODES.S)
+    }
 }
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManager.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManager.kt
index de62bd2..4641d2b 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManager.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManager.kt
@@ -20,18 +20,11 @@
 import android.annotation.SuppressLint
 import android.content.Context
 import android.net.Uri
-import android.os.ext.SdkExtensions
 import android.util.Log
 import android.view.InputEvent
-import androidx.annotation.DoNotInline
-import androidx.annotation.RequiresExtension
 import androidx.annotation.RequiresPermission
-import androidx.core.os.asOutcomeReceiver
 import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
 import androidx.privacysandbox.ads.adservices.internal.AdServicesInfo
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.suspendCancellableCoroutine
 
 /**
  * This class provides APIs to manage ads attribution using Privacy Sandbox.
@@ -103,167 +96,6 @@
     @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
     abstract suspend fun getMeasurementApiStatus(): Int
 
-    @SuppressLint("NewApi", "ClassVerificationFailure")
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
-    private class Api33Ext5Impl(
-        private val mMeasurementManager: android.adservices.measurement.MeasurementManager
-    ) : MeasurementManager() {
-        constructor(context: Context) : this(
-            context.getSystemService<android.adservices.measurement.MeasurementManager>(
-                android.adservices.measurement.MeasurementManager::class.java
-            )
-        )
-
-        @DoNotInline
-        override suspend fun deleteRegistrations(deletionRequest: DeletionRequest) {
-            suspendCancellableCoroutine<Any> { continuation ->
-                mMeasurementManager.deleteRegistrations(
-                    convertDeletionRequest(deletionRequest),
-                    Runnable::run,
-                    continuation.asOutcomeReceiver()
-                )
-            }
-        }
-
-        private fun convertDeletionRequest(
-            request: DeletionRequest
-        ): android.adservices.measurement.DeletionRequest {
-            return android.adservices.measurement.DeletionRequest.Builder()
-                .setDeletionMode(request.deletionMode)
-                .setMatchBehavior(request.matchBehavior)
-                .setStart(request.start)
-                .setEnd(request.end)
-                .setDomainUris(request.domainUris)
-                .setOriginUris(request.originUris)
-                .build()
-        }
-
-        @DoNotInline
-        @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
-        override suspend fun registerSource(attributionSource: Uri, inputEvent: InputEvent?) {
-            suspendCancellableCoroutine<Any> { continuation ->
-                mMeasurementManager.registerSource(
-                    attributionSource,
-                    inputEvent,
-                    Runnable::run,
-                    continuation.asOutcomeReceiver()
-                )
-            }
-        }
-
-        @DoNotInline
-        @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
-        override suspend fun registerTrigger(trigger: Uri) {
-            suspendCancellableCoroutine<Any> { continuation ->
-                mMeasurementManager.registerTrigger(
-                    trigger,
-                    Runnable::run,
-                    continuation.asOutcomeReceiver())
-            }
-        }
-
-        @DoNotInline
-        @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
-        override suspend fun registerWebSource(request: WebSourceRegistrationRequest) {
-            suspendCancellableCoroutine<Any> { continuation ->
-                mMeasurementManager.registerWebSource(
-                    convertWebSourceRequest(request),
-                    Runnable::run,
-                    continuation.asOutcomeReceiver())
-            }
-        }
-
-        @DoNotInline
-        @ExperimentalFeatures.RegisterSourceOptIn
-        @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
-        override suspend fun registerSource(
-            request: SourceRegistrationRequest
-        ): Unit = coroutineScope {
-            request.registrationUris.forEach { uri ->
-                launch {
-                    suspendCancellableCoroutine<Any> { continuation ->
-                        mMeasurementManager.registerSource(
-                            uri,
-                            request.inputEvent,
-                            Runnable::run,
-                            continuation.asOutcomeReceiver()
-                        )
-                    }
-                }
-            }
-        }
-
-        private fun convertWebSourceRequest(
-            request: WebSourceRegistrationRequest
-        ): android.adservices.measurement.WebSourceRegistrationRequest {
-            return android.adservices.measurement.WebSourceRegistrationRequest
-                .Builder(
-                    convertWebSourceParams(request.webSourceParams),
-                    request.topOriginUri)
-                .setWebDestination(request.webDestination)
-                .setAppDestination(request.appDestination)
-                .setInputEvent(request.inputEvent)
-                .setVerifiedDestination(request.verifiedDestination)
-                .build()
-        }
-
-        private fun convertWebSourceParams(
-            request: List<WebSourceParams>
-        ): List<android.adservices.measurement.WebSourceParams> {
-            var result = mutableListOf<android.adservices.measurement.WebSourceParams>()
-            for (param in request) {
-                result.add(android.adservices.measurement.WebSourceParams
-                    .Builder(param.registrationUri)
-                    .setDebugKeyAllowed(param.debugKeyAllowed)
-                    .build())
-            }
-            return result
-        }
-
-        @DoNotInline
-        @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
-        override suspend fun registerWebTrigger(request: WebTriggerRegistrationRequest) {
-            suspendCancellableCoroutine<Any> { continuation ->
-                mMeasurementManager.registerWebTrigger(
-                    convertWebTriggerRequest(request),
-                    Runnable::run,
-                    continuation.asOutcomeReceiver())
-            }
-        }
-
-        private fun convertWebTriggerRequest(
-            request: WebTriggerRegistrationRequest
-        ): android.adservices.measurement.WebTriggerRegistrationRequest {
-            return android.adservices.measurement.WebTriggerRegistrationRequest
-                .Builder(
-                    convertWebTriggerParams(request.webTriggerParams),
-                    request.destination)
-                .build()
-        }
-
-        private fun convertWebTriggerParams(
-            request: List<WebTriggerParams>
-        ): List<android.adservices.measurement.WebTriggerParams> {
-            var result = mutableListOf<android.adservices.measurement.WebTriggerParams>()
-            for (param in request) {
-                result.add(android.adservices.measurement.WebTriggerParams
-                    .Builder(param.registrationUri)
-                    .setDebugKeyAllowed(param.debugKeyAllowed)
-                    .build())
-            }
-            return result
-        }
-
-        @DoNotInline
-        @RequiresPermission(ACCESS_ADSERVICES_ATTRIBUTION)
-        override suspend fun getMeasurementApiStatus(): Int = suspendCancellableCoroutine {
-                continuation ->
-            mMeasurementManager.getMeasurementApiStatus(
-                Runnable::run,
-                continuation.asOutcomeReceiver())
-        }
-    }
-
     companion object {
         /**
          * This state indicates that Measurement APIs are unavailable. Invoking them will result
@@ -284,9 +116,12 @@
         @JvmStatic
         @SuppressLint("NewApi", "ClassVerificationFailure")
         fun obtain(context: Context): MeasurementManager? {
-            Log.d("MeasurementManager", "AdServicesInfo.version=${AdServicesInfo.version()}")
-            return if (AdServicesInfo.version() >= 5) {
-                Api33Ext5Impl(context)
+            Log.d("MeasurementManager",
+                "AdServicesInfo.version=${AdServicesInfo.adServicesVersion()}")
+            return if (AdServicesInfo.adServicesVersion() >= 5) {
+                MeasurementManagerApi33Ext5Impl(context)
+            } else if (AdServicesInfo.extServicesVersion() >= 9) {
+                MeasurementManagerApi31Ext9Impl(context)
             } else {
                 null
             }
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerApi31Ext9Impl.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerApi31Ext9Impl.kt
new file mode 100644
index 0000000..4d55606
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerApi31Ext9Impl.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.
+ */
+
+package androidx.privacysandbox.ads.adservices.measurement
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.Build
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressLint("NewApi", "ClassVerificationFailure")
+@RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+class MeasurementManagerApi31Ext9Impl(context: Context) : MeasurementManagerImplCommon(
+    android.adservices.measurement.MeasurementManager.get(context))
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerApi33Ext5Impl.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerApi33Ext5Impl.kt
new file mode 100644
index 0000000..cf0de76
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerApi33Ext5Impl.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.
+ */
+
+package androidx.privacysandbox.ads.adservices.measurement
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.ext.SdkExtensions
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressLint("NewApi", "ClassVerificationFailure")
+@RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
+class MeasurementManagerApi33Ext5Impl(context: Context) : MeasurementManagerImplCommon(
+    context.getSystemService(android.adservices.measurement.MeasurementManager::class.java))
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerImplCommon.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerImplCommon.kt
new file mode 100644
index 0000000..3618240
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerImplCommon.kt
@@ -0,0 +1,190 @@
+/*
+ * 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.privacysandbox.ads.adservices.measurement
+
+import android.adservices.common.AdServicesPermissions
+import android.annotation.SuppressLint
+import android.net.Uri
+import android.os.Build
+import android.os.ext.SdkExtensions
+import android.view.InputEvent
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RequiresPermission
+import androidx.annotation.RestrictTo
+import androidx.core.os.asOutcomeReceiver
+import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressLint("NewApi", "ClassVerificationFailure")
+@RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
+@RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+open class MeasurementManagerImplCommon(
+    protected val mMeasurementManager: android.adservices.measurement.MeasurementManager
+    ) : MeasurementManager() {
+    @DoNotInline
+    override suspend fun deleteRegistrations(deletionRequest: DeletionRequest) {
+        suspendCancellableCoroutine<Any> { continuation ->
+            mMeasurementManager.deleteRegistrations(
+                convertDeletionRequest(deletionRequest),
+                Runnable::run,
+                continuation.asOutcomeReceiver()
+            )
+        }
+    }
+
+    private fun convertDeletionRequest(
+        request: DeletionRequest
+    ): android.adservices.measurement.DeletionRequest {
+        return android.adservices.measurement.DeletionRequest.Builder()
+            .setDeletionMode(request.deletionMode)
+            .setMatchBehavior(request.matchBehavior)
+            .setStart(request.start)
+            .setEnd(request.end)
+            .setDomainUris(request.domainUris)
+            .setOriginUris(request.originUris)
+            .build()
+    }
+
+    @DoNotInline
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION)
+    override suspend fun registerSource(attributionSource: Uri, inputEvent: InputEvent?) {
+        suspendCancellableCoroutine<Any> { continuation ->
+            mMeasurementManager.registerSource(
+                attributionSource,
+                inputEvent,
+                Runnable::run,
+                continuation.asOutcomeReceiver()
+            )
+        }
+    }
+
+    @DoNotInline
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION)
+    override suspend fun registerTrigger(trigger: Uri) {
+        suspendCancellableCoroutine<Any> { continuation ->
+            mMeasurementManager.registerTrigger(
+                trigger,
+                Runnable::run,
+                continuation.asOutcomeReceiver())
+        }
+    }
+
+    @DoNotInline
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION)
+    override suspend fun registerWebSource(request: WebSourceRegistrationRequest) {
+        suspendCancellableCoroutine<Any> { continuation ->
+            mMeasurementManager.registerWebSource(
+                convertWebSourceRequest(request),
+                Runnable::run,
+                continuation.asOutcomeReceiver())
+        }
+    }
+
+    @DoNotInline
+    @ExperimentalFeatures.RegisterSourceOptIn
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION)
+    override suspend fun registerSource(
+        request: SourceRegistrationRequest
+    ): Unit = coroutineScope {
+        request.registrationUris.forEach { uri ->
+            launch {
+                suspendCancellableCoroutine<Any> { continuation ->
+                    mMeasurementManager.registerSource(
+                        uri,
+                        request.inputEvent,
+                        Runnable::run,
+                        continuation.asOutcomeReceiver()
+                    )
+                }
+            }
+        }
+    }
+
+    private fun convertWebSourceRequest(
+        request: WebSourceRegistrationRequest
+    ): android.adservices.measurement.WebSourceRegistrationRequest {
+        return android.adservices.measurement.WebSourceRegistrationRequest
+            .Builder(
+                convertWebSourceParams(request.webSourceParams),
+                request.topOriginUri)
+            .setWebDestination(request.webDestination)
+            .setAppDestination(request.appDestination)
+            .setInputEvent(request.inputEvent)
+            .setVerifiedDestination(request.verifiedDestination)
+            .build()
+    }
+
+    private fun convertWebSourceParams(
+        request: List<WebSourceParams>
+    ): List<android.adservices.measurement.WebSourceParams> {
+        var result = mutableListOf<android.adservices.measurement.WebSourceParams>()
+        for (param in request) {
+            result.add(android.adservices.measurement.WebSourceParams
+                .Builder(param.registrationUri)
+                .setDebugKeyAllowed(param.debugKeyAllowed)
+                .build())
+        }
+        return result
+    }
+
+    @DoNotInline
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION)
+    override suspend fun registerWebTrigger(request: WebTriggerRegistrationRequest) {
+        suspendCancellableCoroutine<Any> { continuation ->
+            mMeasurementManager.registerWebTrigger(
+                convertWebTriggerRequest(request),
+                Runnable::run,
+                continuation.asOutcomeReceiver())
+        }
+    }
+
+    private fun convertWebTriggerRequest(
+        request: WebTriggerRegistrationRequest
+    ): android.adservices.measurement.WebTriggerRegistrationRequest {
+        return android.adservices.measurement.WebTriggerRegistrationRequest
+            .Builder(
+                convertWebTriggerParams(request.webTriggerParams),
+                request.destination)
+            .build()
+    }
+
+    private fun convertWebTriggerParams(
+        request: List<WebTriggerParams>
+    ): List<android.adservices.measurement.WebTriggerParams> {
+        var result = mutableListOf<android.adservices.measurement.WebTriggerParams>()
+        for (param in request) {
+            result.add(android.adservices.measurement.WebTriggerParams
+                .Builder(param.registrationUri)
+                .setDebugKeyAllowed(param.debugKeyAllowed)
+                .build())
+        }
+        return result
+    }
+
+    @DoNotInline
+    @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION)
+    override suspend fun getMeasurementApiStatus(): Int = suspendCancellableCoroutine {
+            continuation ->
+        mMeasurementManager.getMeasurementApiStatus(
+            Runnable::run,
+            continuation.asOutcomeReceiver())
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/TopicsManager.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/TopicsManager.kt
index 828fdf9..ab533f0 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/TopicsManager.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/TopicsManager.kt
@@ -50,10 +50,12 @@
         @JvmStatic
         @SuppressLint("NewApi", "ClassVerificationFailure")
         fun obtain(context: Context): TopicsManager? {
-            return if (AdServicesInfo.version() >= 5) {
+            return if (AdServicesInfo.adServicesVersion() >= 5) {
                 TopicsManagerApi33Ext5Impl(context)
-            } else if (AdServicesInfo.version() == 4) {
+            } else if (AdServicesInfo.adServicesVersion() == 4) {
                 TopicsManagerApi33Ext4Impl(context)
+            } else if (AdServicesInfo.extServicesVersion() >= 9) {
+                TopicsManagerApi31Ext9Impl(context)
             } else {
                 null
             }
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/TopicsManagerApi31Ext9Impl.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/TopicsManagerApi31Ext9Impl.kt
new file mode 100644
index 0000000..c9aedf9
--- /dev/null
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/TopicsManagerApi31Ext9Impl.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.ads.adservices.topics
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.Build
+import androidx.annotation.RequiresExtension
+import androidx.annotation.RestrictTo
+
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+@SuppressLint("NewApi", "ClassVerificationFailure")
+@RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
+class TopicsManagerApi31Ext9Impl(context: Context) : TopicsManagerImplCommon(
+    android.adservices.topics.TopicsManager.get(context)) {
+
+    override fun convertRequest(
+        request: GetTopicsRequest
+    ): android.adservices.topics.GetTopicsRequest {
+        return android.adservices.topics.GetTopicsRequest.Builder()
+            .setAdsSdkName(request.adsSdkName)
+            .setShouldRecordObservation(request.shouldRecordObservation)
+            .build()
+    }
+}
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/TopicsManagerImplCommon.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/TopicsManagerImplCommon.kt
index 7dc5752..2b09952 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/TopicsManagerImplCommon.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/topics/TopicsManagerImplCommon.kt
@@ -1,7 +1,24 @@
+/*
+ * 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.privacysandbox.ads.adservices.topics
 
 import android.adservices.common.AdServicesPermissions
 import android.annotation.SuppressLint
+import android.os.Build
 import android.os.ext.SdkExtensions
 import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresExtension
@@ -13,6 +30,7 @@
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @SuppressLint("NewApi")
 @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+@RequiresExtension(extension = Build.VERSION_CODES.S, version = 9)
 open class TopicsManagerImplCommon(
     private val mTopicsManager: android.adservices.topics.TopicsManager
 ) : TopicsManager() {
diff --git a/privacysandbox/ui/ui-client/api/current.txt b/privacysandbox/ui/ui-client/api/current.txt
index bc9a40d..47ff65b 100644
--- a/privacysandbox/ui/ui-client/api/current.txt
+++ b/privacysandbox/ui/ui-client/api/current.txt
@@ -48,9 +48,9 @@
     ctor public SandboxedSdkView(android.content.Context context);
     ctor public SandboxedSdkView(android.content.Context context, optional android.util.AttributeSet? attrs);
     method public void addStateChangedListener(androidx.privacysandbox.ui.client.view.SandboxedSdkUiSessionStateChangedListener stateChangedListener);
+    method public void orderProviderUiAboveClientUi(boolean providerUiOnTop);
     method public void removeStateChangedListener(androidx.privacysandbox.ui.client.view.SandboxedSdkUiSessionStateChangedListener stateChangedListener);
     method public void setAdapter(androidx.privacysandbox.ui.core.SandboxedUiAdapter sandboxedUiAdapter);
-    method public void setZOrderOnTopAndEnableUserInteraction(boolean setOnTop);
   }
 
 }
diff --git a/privacysandbox/ui/ui-client/api/restricted_current.txt b/privacysandbox/ui/ui-client/api/restricted_current.txt
index bc9a40d..47ff65b 100644
--- a/privacysandbox/ui/ui-client/api/restricted_current.txt
+++ b/privacysandbox/ui/ui-client/api/restricted_current.txt
@@ -48,9 +48,9 @@
     ctor public SandboxedSdkView(android.content.Context context);
     ctor public SandboxedSdkView(android.content.Context context, optional android.util.AttributeSet? attrs);
     method public void addStateChangedListener(androidx.privacysandbox.ui.client.view.SandboxedSdkUiSessionStateChangedListener stateChangedListener);
+    method public void orderProviderUiAboveClientUi(boolean providerUiOnTop);
     method public void removeStateChangedListener(androidx.privacysandbox.ui.client.view.SandboxedSdkUiSessionStateChangedListener stateChangedListener);
     method public void setAdapter(androidx.privacysandbox.ui.core.SandboxedUiAdapter sandboxedUiAdapter);
-    method public void setZOrderOnTopAndEnableUserInteraction(boolean setOnTop);
   }
 
 }
diff --git a/privacysandbox/ui/ui-client/build.gradle b/privacysandbox/ui/ui-client/build.gradle
index 41e0a3c..fad9e90 100644
--- a/privacysandbox/ui/ui-client/build.gradle
+++ b/privacysandbox/ui/ui-client/build.gradle
@@ -31,7 +31,7 @@
     implementation("androidx.core:core:1.12.0-alpha05")
 
     implementation("androidx.lifecycle:lifecycle-common:2.2.0")
-    implementation project(path: ':privacysandbox:sdkruntime:sdkruntime-client')
+    implementation("androidx.privacysandbox.sdkruntime:sdkruntime-client:1.0.0-alpha08")
     implementation project(path: ':privacysandbox:ui:ui-core')
 
     androidTestImplementation(project(":internal-testutils-runtime"))
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 7498242..8028c83 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
@@ -288,13 +288,13 @@
         assertThat(adapter.isZOrderOnTop).isTrue()
 
         // When state changes to false, the provider should be notified.
-        view.setZOrderOnTopAndEnableUserInteraction(false)
+        view.orderProviderUiAboveClientUi(false)
         assertThat(session.zOrderChangedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
         assertThat(adapter.isZOrderOnTop).isFalse()
 
         // When state changes back to true, the provider should be notified.
         session.zOrderChangedLatch = CountDownLatch(1)
-        view.setZOrderOnTopAndEnableUserInteraction(true)
+        view.orderProviderUiAboveClientUi(true)
         assertThat(session.zOrderChangedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
         assertThat(adapter.isZOrderOnTop).isTrue()
     }
@@ -311,14 +311,14 @@
 
         // When Z-order state is unchanged, the provider should not be notified.
         session.zOrderChangedLatch = CountDownLatch(1)
-        view.setZOrderOnTopAndEnableUserInteraction(true)
+        view.orderProviderUiAboveClientUi(true)
         assertThat(session.zOrderChangedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isFalse()
         assertThat(adapter.isZOrderOnTop).isTrue()
     }
 
     @Test
     fun setZOrderNotOnTopBeforeOpeningSession() {
-        view.setZOrderOnTopAndEnableUserInteraction(false)
+        view.orderProviderUiAboveClientUi(false)
         addViewToLayout()
         assertThat(openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
         val session = testSandboxedUiAdapter.testSession!!
@@ -333,7 +333,7 @@
         testSandboxedUiAdapter.delayOpenSessionCallback = true
         addViewToLayout()
         assertThat(openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
-        view.setZOrderOnTopAndEnableUserInteraction(false)
+        view.orderProviderUiAboveClientUi(false)
         val session = testSandboxedUiAdapter.testSession!!
         assertThat(session.zOrderChangedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isFalse()
         activity.runOnUiThread {
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 34e205b..49c553f 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
@@ -162,14 +162,15 @@
     /**
      * Sets the Z-ordering of the [SandboxedSdkView]'s surface, relative to its window.
      *
-     * When [setOnTop] is true, every [android.view.MotionEvent] on the [SandboxedSdkView] will be
-     * sent to the UI provider. When [setOnTop] is false, every [android.view.MotionEvent] will be
-     * sent to the client. By default, motion events are sent to the UI provider.
+     * When [providerUiOnTop] is true, every [android.view.MotionEvent] on the [SandboxedSdkView]
+     * will be sent to the UI provider. When [providerUiOnTop] is false, every
+     * [android.view.MotionEvent] will be sent to the client. By default, motion events are sent to
+     * the UI provider.
      */
-    fun setZOrderOnTopAndEnableUserInteraction(setOnTop: Boolean) {
-        if (setOnTop == isZOrderOnTop) return
-        client?.notifyZOrderChanged(setOnTop)
-        isZOrderOnTop = setOnTop
+    fun orderProviderUiAboveClientUi(providerUiOnTop: Boolean) {
+        if (providerUiOnTop == isZOrderOnTop) return
+        client?.notifyZOrderChanged(providerUiOnTop)
+        isZOrderOnTop = providerUiOnTop
         checkClientOpenSession()
     }
 
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 66e1da8..77138eb 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
@@ -97,7 +97,8 @@
                     mDisplayManager.getDisplay(displayId), windowInputToken
                 )
                 val sessionClient = SessionClientProxy(
-                    surfaceControlViewHost, initialWidth, initialHeight, remoteSessionClient
+                    surfaceControlViewHost, initialWidth, initialHeight, isZOrderOnTop,
+                    remoteSessionClient
                 )
                 openSession(
                     windowContext, windowInputToken, initialWidth, initialHeight, isZOrderOnTop,
@@ -113,6 +114,7 @@
         private val surfaceControlViewHost: SurfaceControlViewHost,
         private val initialWidth: Int,
         private val initialHeight: Int,
+        private val isZOrderOnTop: Boolean,
         private val remoteSessionClient: IRemoteSessionClient
     ) : SandboxedUiAdapter.SessionClient {
 
@@ -162,7 +164,7 @@
                 RemoteSessionController(surfaceControlViewHost, session)
             remoteSessionClient.onRemoteSessionOpened(
                 surfacePackage, remoteSessionController,
-                /* isZOrderOnTop= */ true
+                isZOrderOnTop
             )
         }
 
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 3333303..1528e16 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
@@ -22,6 +22,8 @@
 import android.os.Binder
 import android.os.Build
 import android.os.IBinder
+import android.os.SystemClock
+import android.view.MotionEvent
 import android.view.View
 import android.view.View.OnLayoutChangeListener
 import android.view.ViewGroup
@@ -218,7 +220,7 @@
         val adapterFromCoreLibInfo = SandboxedUiAdapterFactory.createFromCoreLibInfo(coreLibInfo)
         view.setAdapter(adapterFromCoreLibInfo)
         assertThat(openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
-        view.setZOrderOnTopAndEnableUserInteraction(!adapter.initialZOrderOnTop)
+        view.orderProviderUiAboveClientUi(!adapter.initialZOrderOnTop)
         assertThat(adapter.zOrderLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
     }
 
@@ -237,11 +239,27 @@
         val adapterFromCoreLibInfo = SandboxedUiAdapterFactory.createFromCoreLibInfo(coreLibInfo)
         view.setAdapter(adapterFromCoreLibInfo)
         assertThat(openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
-        view.setZOrderOnTopAndEnableUserInteraction(adapter.initialZOrderOnTop)
+        view.orderProviderUiAboveClientUi(adapter.initialZOrderOnTop)
         assertThat(adapter.zOrderLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isFalse()
     }
 
     @Test
+    fun testHostCanSetZOrderAboveBeforeOpeningSession() {
+        val adapter = openSessionAndWaitToBeActive(true)
+        injectInputEventOnView()
+        // the injected touch should be handled by the provider in Z-above mode
+        assertThat(adapter.touchedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+    }
+
+    @Test
+    fun testHostCanSetZOrderBelowBeforeOpeningSession() {
+        val adapter = openSessionAndWaitToBeActive(false)
+        injectInputEventOnView()
+        // the injected touch should not reach the provider in Z-below mode
+        assertThat(adapter.touchedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isFalse()
+    }
+
+    @Test
     fun testSessionError() {
         val adapter = TestSandboxedUiAdapter(
             null, null, true
@@ -257,6 +275,38 @@
         assertTrue(errorMessage == "Test Session Exception")
     }
 
+    private fun openSessionAndWaitToBeActive(initialZOrder: Boolean): TestSandboxedUiAdapter {
+        val adapter = TestSandboxedUiAdapter(
+            null,
+            null,
+            /* hasFailingTestSession=*/false
+        )
+        val coreLibInfo = adapter.toCoreLibInfo(context)
+        val adapterFromCoreLibInfo = SandboxedUiAdapterFactory.createFromCoreLibInfo(coreLibInfo)
+        view.orderProviderUiAboveClientUi(initialZOrder)
+        view.setAdapter(adapterFromCoreLibInfo)
+        val activeLatch = CountDownLatch(1)
+        view.addStateChangedListener { state ->
+            if (state is SandboxedSdkUiSessionState.Active) {
+                activeLatch.countDown()
+            }
+        }
+        assertThat(activeLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+        return adapter
+    }
+
+    private fun injectInputEventOnView() {
+        activity.runOnUiThread {
+            val location = IntArray(2)
+            view.getLocationOnScreen(location)
+            InstrumentationRegistry.getInstrumentation().uiAutomation.injectInputEvent(
+                MotionEvent.obtain(
+                    SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN,
+                    (location[0] + 1).toFloat(),
+                    (location[1] + 1).toFloat(), 0), false)
+        }
+    }
+
     class TestStateChangeListener(private val errorLatch: CountDownLatch) :
         SandboxedSdkUiSessionStateChangedListener {
         var currentState: SandboxedSdkUiSessionState? = null
@@ -280,6 +330,7 @@
         var isOpenSessionCalled = false
         var initialZOrderOnTop = false
         var zOrderLatch = CountDownLatch(1)
+        var touchedLatch = CountDownLatch(1)
         lateinit var session: SandboxedUiAdapter.Session
         lateinit var internalClient: SandboxedUiAdapter.SessionClient
 
@@ -331,7 +382,12 @@
         ) : SandboxedUiAdapter.Session {
             override val view: View
                 get() {
-                    return View(context)
+                    return View(context).also {
+                        it.setOnTouchListener { _, _ ->
+                            touchedLatch.countDown()
+                            true
+                        }
+                    }
                 }
 
             init {
diff --git a/room/integration-tests/autovaluetestapp/build.gradle b/room/integration-tests/autovaluetestapp/build.gradle
index e44eda7..bec90d2 100644
--- a/room/integration-tests/autovaluetestapp/build.gradle
+++ b/room/integration-tests/autovaluetestapp/build.gradle
@@ -14,13 +14,6 @@
  * limitations under the License.
  */
 
-buildscript {
-    // TODO: Remove this when this test app no longer depends on 1.0.0 of vectordrawable-animated.
-    // vectordrawable and vectordrawable-animated were accidentally using the same package name
-    // which is no longer valid in namespaced resource world.
-    project.ext["android.uniquePackageNames"] = false
-}
-
 plugins {
     id("AndroidXPlugin")
     id("com.android.application")
diff --git a/room/room-common/build.gradle b/room/room-common/build.gradle
index 4f3fd52..49e5177 100644
--- a/room/room-common/build.gradle
+++ b/room/room-common/build.gradle
@@ -14,27 +14,52 @@
  * limitations under the License.
  */
 
+
+import androidx.build.LibraryType
+import androidx.build.PlatformIdentifier
 import androidx.build.Publish
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
-    id("kotlin")
 }
 
-dependencies {
-    api("androidx.annotation:annotation:1.3.0")
-    api(libs.kotlinStdlibJdk8)
-    testImplementation(libs.junit)
-    testImplementation(libs.mockitoCore4)
-    testImplementation(libs.guava)
-    testImplementation(project(":kruth:kruth"))
+androidXMultiplatform {
+    jvm() {
+        withJava()
+    }
+    mac()
+    linux()
+    ios()
+
+    defaultPlatform(PlatformIdentifier.JVM)
+
+    sourceSets {
+        all {
+            languageSettings.optIn("kotlin.RequiresOptIn")
+        }
+
+        commonMain {
+            dependencies {
+                api(libs.kotlinStdlib)
+                api("androidx.annotation:annotation:1.3.0")
+            }
+        }
+        commonTest {
+            dependencies {
+                implementation(project(":kruth:kruth"))
+                implementation(libs.junit)
+                implementation(libs.guava)
+            }
+        }
+    }
 }
 
 androidx {
     name = "Room-Common"
+    type = LibraryType.PUBLISHED_LIBRARY
     publish = Publish.SNAPSHOT_AND_RELEASE
     inceptionYear = "2017"
     description = "Android Room-Common"
+    legacyDisableKotlinStrictApiMode = true
     metalavaK2UastEnabled = true
 }
diff --git a/room/room-common/src/main/java/androidx/room/AmbiguousColumnResolver.kt b/room/room-common/src/commonMain/kotlin/androidx/room/AmbiguousColumnResolver.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/AmbiguousColumnResolver.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/AmbiguousColumnResolver.kt
diff --git a/room/room-common/src/main/java/androidx/room/AutoMigration.kt b/room/room-common/src/commonMain/kotlin/androidx/room/AutoMigration.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/AutoMigration.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/AutoMigration.kt
diff --git a/room/room-common/src/main/java/androidx/room/BuiltInTypeConverters.kt b/room/room-common/src/commonMain/kotlin/androidx/room/BuiltInTypeConverters.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/BuiltInTypeConverters.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/BuiltInTypeConverters.kt
diff --git a/room/room-common/src/main/java/androidx/room/ColumnInfo.kt b/room/room-common/src/commonMain/kotlin/androidx/room/ColumnInfo.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/ColumnInfo.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/ColumnInfo.kt
diff --git a/room/room-common/src/main/java/androidx/room/Dao.kt b/room/room-common/src/commonMain/kotlin/androidx/room/Dao.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/Dao.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/Dao.kt
diff --git a/room/room-common/src/main/java/androidx/room/Database.kt b/room/room-common/src/commonMain/kotlin/androidx/room/Database.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/Database.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/Database.kt
diff --git a/room/room-common/src/main/java/androidx/room/DatabaseView.kt b/room/room-common/src/commonMain/kotlin/androidx/room/DatabaseView.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/DatabaseView.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/DatabaseView.kt
diff --git a/room/room-common/src/main/java/androidx/room/Delete.kt b/room/room-common/src/commonMain/kotlin/androidx/room/Delete.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/Delete.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/Delete.kt
diff --git a/room/room-common/src/main/java/androidx/room/DeleteColumn.kt b/room/room-common/src/commonMain/kotlin/androidx/room/DeleteColumn.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/DeleteColumn.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/DeleteColumn.kt
diff --git a/room/room-common/src/main/java/androidx/room/DeleteTable.kt b/room/room-common/src/commonMain/kotlin/androidx/room/DeleteTable.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/DeleteTable.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/DeleteTable.kt
diff --git a/room/room-common/src/main/java/androidx/room/Embedded.kt b/room/room-common/src/commonMain/kotlin/androidx/room/Embedded.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/Embedded.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/Embedded.kt
diff --git a/room/room-common/src/main/java/androidx/room/Entity.kt b/room/room-common/src/commonMain/kotlin/androidx/room/Entity.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/Entity.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/Entity.kt
diff --git a/room/room-common/src/main/java/androidx/room/ForeignKey.kt b/room/room-common/src/commonMain/kotlin/androidx/room/ForeignKey.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/ForeignKey.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/ForeignKey.kt
diff --git a/room/room-common/src/main/java/androidx/room/Fts3.kt b/room/room-common/src/commonMain/kotlin/androidx/room/Fts3.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/Fts3.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/Fts3.kt
diff --git a/room/room-common/src/main/java/androidx/room/Fts4.kt b/room/room-common/src/commonMain/kotlin/androidx/room/Fts4.kt
similarity index 97%
rename from room/room-common/src/main/java/androidx/room/Fts4.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/Fts4.kt
index f9ed6e1..fcd8895 100644
--- a/room/room-common/src/main/java/androidx/room/Fts4.kt
+++ b/room/room-common/src/commonMain/kotlin/androidx/room/Fts4.kt
@@ -17,8 +17,6 @@
 package androidx.room
 
 import androidx.annotation.RequiresApi
-import androidx.room.FtsOptions.MatchInfo
-import androidx.room.FtsOptions.Order
 import androidx.room.FtsOptions.TOKENIZER_SIMPLE
 import kotlin.reflect.KClass
 
@@ -144,7 +142,7 @@
      *
      * @return The match info version, either [MatchInfo.FTS4] or [MatchInfo.FTS3].
      */
-    val matchInfo: MatchInfo = MatchInfo.FTS4,
+    val matchInfo: FtsOptions.MatchInfo = FtsOptions.MatchInfo.FTS4,
 
     /**
      * The list of column names on the FTS table that won't be indexed.
@@ -176,5 +174,5 @@
      *
      * @return The preferred order, either [Order.ASC] or [Order.DESC].
      */
-    val order: Order = Order.ASC
+    val order: FtsOptions.Order = FtsOptions.Order.ASC
 )
diff --git a/room/room-common/src/main/java/androidx/room/FtsOptions.kt b/room/room-common/src/commonMain/kotlin/androidx/room/FtsOptions.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/FtsOptions.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/FtsOptions.kt
diff --git a/room/room-common/src/main/java/androidx/room/Ignore.kt b/room/room-common/src/commonMain/kotlin/androidx/room/Ignore.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/Ignore.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/Ignore.kt
diff --git a/room/room-common/src/main/java/androidx/room/Index.kt b/room/room-common/src/commonMain/kotlin/androidx/room/Index.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/Index.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/Index.kt
diff --git a/room/room-common/src/main/java/androidx/room/Insert.kt b/room/room-common/src/commonMain/kotlin/androidx/room/Insert.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/Insert.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/Insert.kt
diff --git a/room/room-common/src/main/java/androidx/room/Junction.kt b/room/room-common/src/commonMain/kotlin/androidx/room/Junction.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/Junction.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/Junction.kt
diff --git a/room/room-common/src/main/java/androidx/room/MapColumn.kt b/room/room-common/src/commonMain/kotlin/androidx/room/MapColumn.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/MapColumn.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/MapColumn.kt
diff --git a/room/room-common/src/main/java/androidx/room/MapInfo.kt b/room/room-common/src/commonMain/kotlin/androidx/room/MapInfo.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/MapInfo.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/MapInfo.kt
diff --git a/room/room-common/src/main/java/androidx/room/OnConflictStrategy.kt b/room/room-common/src/commonMain/kotlin/androidx/room/OnConflictStrategy.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/OnConflictStrategy.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/OnConflictStrategy.kt
diff --git a/room/room-common/src/main/java/androidx/room/PrimaryKey.kt b/room/room-common/src/commonMain/kotlin/androidx/room/PrimaryKey.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/PrimaryKey.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/PrimaryKey.kt
diff --git a/room/room-common/src/main/java/androidx/room/ProvidedAutoMigrationSpec.kt b/room/room-common/src/commonMain/kotlin/androidx/room/ProvidedAutoMigrationSpec.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/ProvidedAutoMigrationSpec.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/ProvidedAutoMigrationSpec.kt
diff --git a/room/room-common/src/main/java/androidx/room/ProvidedTypeConverter.kt b/room/room-common/src/commonMain/kotlin/androidx/room/ProvidedTypeConverter.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/ProvidedTypeConverter.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/ProvidedTypeConverter.kt
diff --git a/room/room-common/src/main/java/androidx/room/Query.kt b/room/room-common/src/commonMain/kotlin/androidx/room/Query.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/Query.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/Query.kt
diff --git a/room/room-common/src/main/java/androidx/room/RawQuery.kt b/room/room-common/src/commonMain/kotlin/androidx/room/RawQuery.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/RawQuery.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/RawQuery.kt
diff --git a/room/room-common/src/main/java/androidx/room/Relation.kt b/room/room-common/src/commonMain/kotlin/androidx/room/Relation.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/Relation.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/Relation.kt
diff --git a/room/room-common/src/main/java/androidx/room/RenameColumn.kt b/room/room-common/src/commonMain/kotlin/androidx/room/RenameColumn.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/RenameColumn.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/RenameColumn.kt
diff --git a/room/room-common/src/main/java/androidx/room/RenameTable.kt b/room/room-common/src/commonMain/kotlin/androidx/room/RenameTable.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/RenameTable.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/RenameTable.kt
diff --git a/room/room-common/src/main/java/androidx/room/RewriteQueriesToDropUnusedColumns.kt b/room/room-common/src/commonMain/kotlin/androidx/room/RewriteQueriesToDropUnusedColumns.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/RewriteQueriesToDropUnusedColumns.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/RewriteQueriesToDropUnusedColumns.kt
diff --git a/room/room-common/src/main/java/androidx/room/RoomMasterTable.kt b/room/room-common/src/commonMain/kotlin/androidx/room/RoomMasterTable.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/RoomMasterTable.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/RoomMasterTable.kt
diff --git a/room/room-common/src/main/java/androidx/room/RoomWarnings.kt b/room/room-common/src/commonMain/kotlin/androidx/room/RoomWarnings.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/RoomWarnings.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/RoomWarnings.kt
diff --git a/room/room-common/src/main/java/androidx/room/SkipQueryVerification.kt b/room/room-common/src/commonMain/kotlin/androidx/room/SkipQueryVerification.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/SkipQueryVerification.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/SkipQueryVerification.kt
diff --git a/room/room-common/src/main/java/androidx/room/Transaction.kt b/room/room-common/src/commonMain/kotlin/androidx/room/Transaction.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/Transaction.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/Transaction.kt
diff --git a/room/room-common/src/main/java/androidx/room/TypeConverter.kt b/room/room-common/src/commonMain/kotlin/androidx/room/TypeConverter.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/TypeConverter.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/TypeConverter.kt
diff --git a/room/room-common/src/main/java/androidx/room/TypeConverters.kt b/room/room-common/src/commonMain/kotlin/androidx/room/TypeConverters.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/TypeConverters.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/TypeConverters.kt
diff --git a/room/room-common/src/main/java/androidx/room/Update.kt b/room/room-common/src/commonMain/kotlin/androidx/room/Update.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/Update.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/Update.kt
diff --git a/room/room-common/src/main/java/androidx/room/Upsert.kt b/room/room-common/src/commonMain/kotlin/androidx/room/Upsert.kt
similarity index 100%
rename from room/room-common/src/main/java/androidx/room/Upsert.kt
rename to room/room-common/src/commonMain/kotlin/androidx/room/Upsert.kt
diff --git a/room/room-common/src/test/java/androidx/room/AmbiguousColumnResolverTest.kt b/room/room-common/src/commonTest/kotlin/androidx/room/AmbiguousColumnResolverTest.kt
similarity index 100%
rename from room/room-common/src/test/java/androidx/room/AmbiguousColumnResolverTest.kt
rename to room/room-common/src/commonTest/kotlin/androidx/room/AmbiguousColumnResolverTest.kt
diff --git a/room/room-common/src/test/java/androidx/room/AnnotationRetentionPolicyTest.kt b/room/room-common/src/commonTest/kotlin/androidx/room/AnnotationRetentionPolicyTest.kt
similarity index 100%
rename from room/room-common/src/test/java/androidx/room/AnnotationRetentionPolicyTest.kt
rename to room/room-common/src/commonTest/kotlin/androidx/room/AnnotationRetentionPolicyTest.kt
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeJavaPoetExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeJavaPoetExt.kt
index 442930d..44457bd 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeJavaPoetExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeJavaPoetExt.kt
@@ -179,7 +179,10 @@
 ): JTypeName {
     return if (declaration is KSTypeAlias) {
         replaceTypeAliases(resolver).asJTypeName(resolver, typeResolutionContext)
-    } else if (this.arguments.isNotEmpty() && !resolver.isJavaRawType(this)) {
+    } else if (this.arguments.isNotEmpty() && !resolver.isJavaRawType(this) &&
+            // Excluding generic value classes otherwise we may generate something
+            // like `Object<String>`.
+            !declaration.isValueClass()) {
         val args: Array<JTypeName> = this.arguments
             .map { typeArg -> typeArg.asJTypeName(resolver, typeResolutionContext) }
             .map { it.tryBox() }
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
index 1a68ad3..af27478 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
@@ -39,6 +39,8 @@
 import androidx.room.compiler.processing.util.runKspTest
 import androidx.room.compiler.processing.util.runProcessorTest
 import com.google.devtools.ksp.getClassDeclarationByName
+import com.google.testing.junit.testparameterinjector.TestParameter
+import com.google.testing.junit.testparameterinjector.TestParameterInjector
 import com.squareup.javapoet.ClassName
 import com.squareup.javapoet.ParameterizedTypeName
 import com.squareup.javapoet.TypeVariableName
@@ -56,9 +58,8 @@
 import com.squareup.kotlinpoet.javapoet.KTypeVariableName
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 
-@RunWith(JUnit4::class)
+@RunWith(TestParameterInjector::class)
 class XTypeTest {
     @Test
     fun typeArguments() {
@@ -1842,22 +1843,57 @@
     }
 
     @Test
-    fun valueTypes() {
+    fun valueTypes(@TestParameter isPrecompiled: Boolean,) {
         val kotlinSrc = Source.kotlin(
             "KotlinClass.kt",
             """
             @JvmInline value class PackageName(val value: String)
+            @JvmInline value class MyResult<T>(val value: T)
 
             class KotlinClass {
+                // @JvmName disables name mangling for functions that use inline classes and
+                // make them visible to Java:
+                // https://kotlinlang.org/docs/inline-classes.html#calling-from-java-code
+                @JvmName("getResult")
+                fun getResult(): MyResult<String> = TODO()
+                @JvmName("setResult")
+                fun setResult(result: MyResult<String>) { }
                 fun getPackageNames(): Set<PackageName> = emptySet()
                 fun setPackageNames(pkgNames: Set<PackageName>) { }
             }
             """.trimIndent()
         )
         runProcessorTest(
-            sources = listOf(kotlinSrc)
+            sources = if (isPrecompiled) { emptyList() } else { listOf(kotlinSrc) },
+            classpath = if (isPrecompiled) { compileFiles(listOf(kotlinSrc)) } else { emptyList() }
         ) { invocation ->
             val kotlinElm = invocation.processingEnv.requireTypeElement("KotlinClass")
+
+            kotlinElm.getDeclaredMethodByJvmName("getResult").apply {
+                assertThat(returnType.asTypeName().java.toString())
+                    .isEqualTo("java.lang.Object")
+                if (invocation.isKsp) {
+                    assertThat(returnType.asTypeName().kotlin.toString())
+                        .isEqualTo("MyResult<kotlin.String>")
+                } else {
+                    // Can't generate Kotlin code with KAPT
+                    assertThat(returnType.asTypeName().kotlin.toString())
+                        .isEqualTo("androidx.room.compiler.codegen.Unavailable")
+                }
+            }
+            kotlinElm.getDeclaredMethodByJvmName("setResult").apply {
+                assertThat(parameters.single().type.asTypeName().java.toString())
+                    .isEqualTo("java.lang.Object")
+                if (invocation.isKsp) {
+                    assertThat(parameters.single().type.asTypeName().kotlin.toString())
+                        .isEqualTo("MyResult<kotlin.String>")
+                } else {
+                    // Can't generate Kotlin code with KAPT
+                    assertThat(parameters.single().type.asTypeName().kotlin.toString())
+                        .isEqualTo("androidx.room.compiler.codegen.Unavailable")
+                }
+            }
+
             kotlinElm.getMethodByJvmName("getPackageNames").apply {
                 assertThat(returnType.typeName.toString())
                     .isEqualTo("java.util.Set<PackageName>")
diff --git a/room/room-runtime/lint-baseline.xml b/room/room-runtime/lint-baseline.xml
index c295a53..5b31a9b 100644
--- a/room/room-runtime/lint-baseline.xml
+++ b/room/room-runtime/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.3.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
 
     <issue
         id="NewApi"
@@ -11,6 +11,96 @@
     </issue>
 
     <issue
+        id="WrongConstant"
+        message="Must be one of: RoomSQLiteQuery.NULL, RoomSQLiteQuery.LONG, RoomSQLiteQuery.DOUBLE, RoomSQLiteQuery.STRING, RoomSQLiteQuery.BLOB"
+        errorLine1="        bindingTypes = IntArray(limit)"
+        errorLine2="        ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/room/RoomSQLiteQuery.kt"/>
+    </issue>
+
+    <issue
+        id="WrongConstant"
+        message="Must be one of: RoomSQLiteQuery.NULL, RoomSQLiteQuery.LONG, RoomSQLiteQuery.DOUBLE, RoomSQLiteQuery.STRING, RoomSQLiteQuery.BLOB"
+        errorLine1="            when (bindingTypes[index]) {"
+        errorLine2="                  ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/room/RoomSQLiteQuery.kt"/>
+    </issue>
+
+    <issue
+        id="WrongConstant"
+        message="Must be one of: RoomSQLiteQuery.NULL, RoomSQLiteQuery.LONG, RoomSQLiteQuery.DOUBLE, RoomSQLiteQuery.STRING, RoomSQLiteQuery.BLOB"
+        errorLine1="        bindingTypes[index] = NULL"
+        errorLine2="        ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/room/RoomSQLiteQuery.kt"/>
+    </issue>
+
+    <issue
+        id="WrongConstant"
+        message="Must be one of: RoomSQLiteQuery.NULL, RoomSQLiteQuery.LONG, RoomSQLiteQuery.DOUBLE, RoomSQLiteQuery.STRING, RoomSQLiteQuery.BLOB"
+        errorLine1="        bindingTypes[index] = LONG"
+        errorLine2="        ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/room/RoomSQLiteQuery.kt"/>
+    </issue>
+
+    <issue
+        id="WrongConstant"
+        message="Must be one of: RoomSQLiteQuery.NULL, RoomSQLiteQuery.LONG, RoomSQLiteQuery.DOUBLE, RoomSQLiteQuery.STRING, RoomSQLiteQuery.BLOB"
+        errorLine1="        bindingTypes[index] = DOUBLE"
+        errorLine2="        ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/room/RoomSQLiteQuery.kt"/>
+    </issue>
+
+    <issue
+        id="WrongConstant"
+        message="Must be one of: RoomSQLiteQuery.NULL, RoomSQLiteQuery.LONG, RoomSQLiteQuery.DOUBLE, RoomSQLiteQuery.STRING, RoomSQLiteQuery.BLOB"
+        errorLine1="        bindingTypes[index] = STRING"
+        errorLine2="        ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/room/RoomSQLiteQuery.kt"/>
+    </issue>
+
+    <issue
+        id="WrongConstant"
+        message="Must be one of: RoomSQLiteQuery.NULL, RoomSQLiteQuery.LONG, RoomSQLiteQuery.DOUBLE, RoomSQLiteQuery.STRING, RoomSQLiteQuery.BLOB"
+        errorLine1="        bindingTypes[index] = BLOB"
+        errorLine2="        ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/room/RoomSQLiteQuery.kt"/>
+    </issue>
+
+    <issue
+        id="WrongConstant"
+        message="Must be one of: RoomSQLiteQuery.NULL, RoomSQLiteQuery.LONG, RoomSQLiteQuery.DOUBLE, RoomSQLiteQuery.STRING, RoomSQLiteQuery.BLOB"
+        errorLine1="        System.arraycopy(other.bindingTypes, 0, bindingTypes, 0, argCount)"
+        errorLine2="                               ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/room/RoomSQLiteQuery.kt"/>
+    </issue>
+
+    <issue
+        id="WrongConstant"
+        message="Must be one of: RoomSQLiteQuery.NULL, RoomSQLiteQuery.LONG, RoomSQLiteQuery.DOUBLE, RoomSQLiteQuery.STRING, RoomSQLiteQuery.BLOB"
+        errorLine1="        System.arraycopy(other.bindingTypes, 0, bindingTypes, 0, argCount)"
+        errorLine2="                                                ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/room/RoomSQLiteQuery.kt"/>
+    </issue>
+
+    <issue
+        id="WrongConstant"
+        message="Must be one of: RoomSQLiteQuery.NULL, RoomSQLiteQuery.LONG, RoomSQLiteQuery.DOUBLE, RoomSQLiteQuery.STRING, RoomSQLiteQuery.BLOB"
+        errorLine1="        Arrays.fill(bindingTypes, NULL)"
+        errorLine2="                    ~~~~~~~~~~~~">
+        <location
+            file="src/main/java/androidx/room/RoomSQLiteQuery.kt"/>
+    </issue>
+
+    <issue
         id="BanThreadSleep"
         message="Uses Thread.sleep()"
         errorLine1="        Thread.sleep(5)"
diff --git a/samples/SupportLeanbackDemos/lint-baseline.xml b/samples/SupportLeanbackDemos/lint-baseline.xml
index 213e53b..0b9624b 100644
--- a/samples/SupportLeanbackDemos/lint-baseline.xml
+++ b/samples/SupportLeanbackDemos/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.3.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
 
     <issue
         id="MissingSuperCall"
@@ -1354,15 +1354,6 @@
     <issue
         id="UnknownNullness"
         message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setItem(PhotoItem photoItem) {"
-        errorLine2="                        ~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/DetailsFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
         errorLine1="    protected void onCreate(Bundle savedInstanceState) {"
         errorLine2="                            ~~~~~~">
         <location
@@ -1444,8 +1435,8 @@
     <issue
         id="UnknownNullness"
         message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setItem(PhotoItem photoItem) {"
-        errorLine2="                        ~~~~~~~~~">
+        errorLine1="    public void onSaveInstanceState(Bundle outState) {"
+        errorLine2="                                    ~~~~~~">
         <location
             file="src/main/java/com/example/android/leanback/DetailsSupportFragment.java"/>
     </issue>
@@ -1474,393 +1465,6 @@
         errorLine1="    protected void onCreate(Bundle savedInstanceState) {"
         errorLine2="                            ~~~~~~">
         <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void onConfigurationChanged(Configuration newConfig) {"
-        errorLine2="                                       ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected void onSaveInstanceState(Bundle outState) {"
-        errorLine2="                                       ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected void onRestoreInstanceState(Bundle savedInstanceState) {"
-        errorLine2="                                          ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public Guidance onCreateGuidance(Bundle savedInstanceState) {"
-        errorLine2="               ~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public Guidance onCreateGuidance(Bundle savedInstanceState) {"
-        errorLine2="                                         ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                    ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                                                ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onGuidedActionClicked(GuidedAction action) {"
-        errorLine2="                                          ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreate(Bundle savedInstance) {"
-        errorLine2="                             ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public Guidance onCreateGuidance(Bundle savedInstanceState) {"
-        errorLine2="               ~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public Guidance onCreateGuidance(Bundle savedInstanceState) {"
-        errorLine2="                                         ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                    ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                                                ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateButtonActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                          ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateButtonActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                                                      ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onGuidedActionClicked(GuidedAction action) {"
-        errorLine2="                                          ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public long onGuidedActionEditedAndProceed(GuidedAction action) {"
-        errorLine2="                                                   ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public GuidedActionsStylist onCreateActionsStylist() {"
-        errorLine2="               ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public Guidance onCreateGuidance(Bundle savedInstanceState) {"
-        errorLine2="               ~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public Guidance onCreateGuidance(Bundle savedInstanceState) {"
-        errorLine2="                                         ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                    ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                                                ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateButtonActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                          ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateButtonActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                                                      ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onGuidedActionClicked(GuidedAction action) {"
-        errorLine2="                                          ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public long onGuidedActionEditedAndProceed(GuidedAction action) {"
-        errorLine2="                                                   ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public boolean onSubGuidedActionClicked(GuidedAction action) {"
-        errorLine2="                                                ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public View onCreateView(LayoutInflater inflater, ViewGroup container,"
-        errorLine2="               ~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public View onCreateView(LayoutInflater inflater, ViewGroup container,"
-        errorLine2="                                                          ~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public Guidance onCreateGuidance(Bundle savedInstanceState) {"
-        errorLine2="               ~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public Guidance onCreateGuidance(Bundle savedInstanceState) {"
-        errorLine2="                                         ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public GuidanceStylist onCreateGuidanceStylist() {"
-        errorLine2="               ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                    ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                                                ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateButtonActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                          ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateButtonActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                                                      ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onGuidedActionClicked(GuidedAction action) {"
-        errorLine2="                                          ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public Guidance onCreateGuidance(Bundle savedInstanceState) {"
-        errorLine2="               ~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public Guidance onCreateGuidance(Bundle savedInstanceState) {"
-        errorLine2="                                         ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                    ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                                                ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onGuidedActionClicked(GuidedAction action) {"
-        errorLine2="                                          ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected void onCreate(Bundle savedInstanceState) {"
-        errorLine2="                            ~~~~~~">
-        <location
             file="src/main/java/com/example/android/leanback/GuidedStepHalfScreenActivity.java"/>
     </issue>
 
@@ -1885,33 +1489,6 @@
     <issue
         id="UnknownNullness"
         message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                    ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepHalfScreenActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                                                ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepHalfScreenActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onGuidedActionClicked(GuidedAction action) {"
-        errorLine2="                                          ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepHalfScreenActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
         errorLine1="        public Guidance onCreateGuidance(Bundle savedInstanceState) {"
         errorLine2="               ~~~~~~~~">
         <location
@@ -1930,357 +1507,6 @@
     <issue
         id="UnknownNullness"
         message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                    ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepHalfScreenActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                                                ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepHalfScreenActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateButtonActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                          ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepHalfScreenActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateButtonActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                                                      ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepHalfScreenActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onGuidedActionClicked(GuidedAction action) {"
-        errorLine2="                                          ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepHalfScreenActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected void onCreate(Bundle savedInstanceState) {"
-        errorLine2="                            ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void onConfigurationChanged(Configuration newConfig) {"
-        errorLine2="                                       ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected void onSaveInstanceState(Bundle outState) {"
-        errorLine2="                                       ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected void onRestoreInstanceState(Bundle savedInstanceState) {"
-        errorLine2="                                          ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateActions(@NonNull List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                                                         ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onGuidedActionClicked(GuidedAction action) {"
-        errorLine2="                                          ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreate(Bundle savedInstance) {"
-        errorLine2="                             ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public Guidance onCreateGuidance(Bundle savedInstanceState) {"
-        errorLine2="                                         ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateActions(@NonNull List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                                                         ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateButtonActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                          ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateButtonActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                                                      ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onGuidedActionClicked(GuidedAction action) {"
-        errorLine2="                                          ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public long onGuidedActionEditedAndProceed(GuidedAction action) {"
-        errorLine2="                                                   ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public GuidedActionsStylist onCreateActionsStylist() {"
-        errorLine2="               ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public Guidance onCreateGuidance(Bundle savedInstanceState) {"
-        errorLine2="                                         ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateActions(@NonNull List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                                                         ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateButtonActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                          ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateButtonActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                                                      ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onGuidedActionClicked(GuidedAction action) {"
-        errorLine2="                                          ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public long onGuidedActionEditedAndProceed(GuidedAction action) {"
-        errorLine2="                                                   ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public boolean onSubGuidedActionClicked(GuidedAction action) {"
-        errorLine2="                                                ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public View onCreateView(LayoutInflater inflater, ViewGroup container,"
-        errorLine2="               ~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public View onCreateView(LayoutInflater inflater, ViewGroup container,"
-        errorLine2="                                 ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public View onCreateView(LayoutInflater inflater, ViewGroup container,"
-        errorLine2="                                                          ~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="                Bundle savedInstanceState) {"
-        errorLine2="                ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public Guidance onCreateGuidance(Bundle savedInstanceState) {"
-        errorLine2="                                         ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public GuidanceStylist onCreateGuidanceStylist() {"
-        errorLine2="               ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateActions(@NonNull List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                                                         ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateButtonActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                          ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateButtonActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                                                      ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onGuidedActionClicked(GuidedAction action) {"
-        errorLine2="                                          ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public Guidance onCreateGuidance(Bundle savedInstanceState) {"
-        errorLine2="                                         ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateActions(@NonNull List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                                                         ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onGuidedActionClicked(GuidedAction action) {"
-        errorLine2="                                          ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
         errorLine1="    protected void onCreate(Bundle savedInstanceState) {"
         errorLine2="                            ~~~~~~">
         <location
@@ -2291,16 +1517,7 @@
         id="UnknownNullness"
         message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
         errorLine1="        public Guidance onCreateGuidance(Bundle savedInstanceState) {"
-        errorLine2="                                         ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportHalfScreenActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onGuidedActionClicked(GuidedAction action) {"
-        errorLine2="                                          ~~~~~~~~~~~~">
+        errorLine2="               ~~~~~~~~">
         <location
             file="src/main/java/com/example/android/leanback/GuidedStepSupportHalfScreenActivity.java"/>
     </issue>
@@ -2317,8 +1534,8 @@
     <issue
         id="UnknownNullness"
         message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateActions(@NonNull List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                                                         ~~~~~~">
+        errorLine1="        public Guidance onCreateGuidance(Bundle savedInstanceState) {"
+        errorLine2="               ~~~~~~~~">
         <location
             file="src/main/java/com/example/android/leanback/GuidedStepSupportHalfScreenActivity.java"/>
     </issue>
@@ -2326,26 +1543,8 @@
     <issue
         id="UnknownNullness"
         message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateButtonActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                          ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportHalfScreenActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onCreateButtonActions(List&lt;GuidedAction> actions, Bundle savedInstanceState) {"
-        errorLine2="                                                                      ~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/GuidedStepSupportHalfScreenActivity.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void onGuidedActionClicked(GuidedAction action) {"
-        errorLine2="                                          ~~~~~~~~~~~~">
+        errorLine1="        public Guidance onCreateGuidance(Bundle savedInstanceState) {"
+        errorLine2="                                         ~~~~~~">
         <location
             file="src/main/java/com/example/android/leanback/GuidedStepSupportHalfScreenActivity.java"/>
     </issue>
@@ -2731,60 +1930,6 @@
     <issue
         id="UnknownNullness"
         message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected View onCreateContentView(LayoutInflater layoutInflater, ViewGroup viewGroup) {"
-        errorLine2="              ~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/OnboardingDemoFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected View onCreateContentView(LayoutInflater layoutInflater, ViewGroup viewGroup) {"
-        errorLine2="                                       ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/OnboardingDemoFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected View onCreateContentView(LayoutInflater layoutInflater, ViewGroup viewGroup) {"
-        errorLine2="                                                                      ~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/OnboardingDemoFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected View onCreateForegroundView(LayoutInflater layoutInflater, ViewGroup viewGroup) {"
-        errorLine2="              ~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/OnboardingDemoFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected View onCreateForegroundView(LayoutInflater layoutInflater, ViewGroup viewGroup) {"
-        errorLine2="                                          ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/OnboardingDemoFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected View onCreateForegroundView(LayoutInflater layoutInflater, ViewGroup viewGroup) {"
-        errorLine2="                                                                         ~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/android/leanback/OnboardingDemoFragment.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
         errorLine1="    protected Animator onCreateEnterAnimation() {"
         errorLine2="              ~~~~~~~~">
         <location
@@ -2821,7 +1966,7 @@
     <issue
         id="UnknownNullness"
         message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected View onCreateBackgroundView("
+        errorLine1="    protected View onCreateBackgroundView(LayoutInflater layoutInflater, ViewGroup viewGroup) {"
         errorLine2="              ~~~~">
         <location
             file="src/main/java/com/example/android/leanback/OnboardingDemoSupportFragment.java"/>
@@ -2830,8 +1975,8 @@
     <issue
         id="UnknownNullness"
         message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected View onCreateContentView("
-        errorLine2="              ~~~~">
+        errorLine1="    protected View onCreateBackgroundView(LayoutInflater layoutInflater, ViewGroup viewGroup) {"
+        errorLine2="                                          ~~~~~~~~~~~~~~">
         <location
             file="src/main/java/com/example/android/leanback/OnboardingDemoSupportFragment.java"/>
     </issue>
@@ -2839,8 +1984,8 @@
     <issue
         id="UnknownNullness"
         message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected View onCreateForegroundView("
-        errorLine2="              ~~~~">
+        errorLine1="    protected View onCreateBackgroundView(LayoutInflater layoutInflater, ViewGroup viewGroup) {"
+        errorLine2="                                                                         ~~~~~~~~~">
         <location
             file="src/main/java/com/example/android/leanback/OnboardingDemoSupportFragment.java"/>
     </issue>
diff --git a/settings.gradle b/settings.gradle
index 7459ae1..571ab12 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -897,7 +897,7 @@
 includeProject(":paging:paging-rxjava2-ktx", [BuildType.MAIN])
 includeProject(":paging:paging-rxjava3", [BuildType.MAIN])
 includeProject(":paging:paging-samples", "paging/samples", [BuildType.MAIN, BuildType.COMPOSE])
-includeProject(":paging:paging-testing", [BuildType.MAIN, BuildType.COMPOSE])
+includeProject(":paging:paging-testing", [BuildType.MAIN, BuildType.COMPOSE, BuildType.INFRAROGUE, BuildType.KMP])
 includeProject(":palette:palette", [BuildType.MAIN])
 includeProject(":palette:palette-ktx", [BuildType.MAIN])
 includeProject(":percentlayout:percentlayout", [BuildType.MAIN])
@@ -948,7 +948,7 @@
 includeProject(":room:integration-tests:room-testapp-kotlin", "room/integration-tests/kotlintestapp", [BuildType.MAIN])
 includeProject(":room:integration-tests:room-testapp-noappcompat", "room/integration-tests/noappcompattestapp", [BuildType.MAIN])
 includeProject(":room:room-benchmark", "room/benchmark", [BuildType.MAIN])
-includeProject(":room:room-common", [BuildType.MAIN, BuildType.COMPOSE])
+includeProject(":room:room-common", [BuildType.MAIN, BuildType.COMPOSE, BuildType.KMP])
 includeProject(":room:room-compiler", [BuildType.MAIN, BuildType.COMPOSE])
 includeProject(":room:room-compiler-processing", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN])
 includeProject(":room:room-compiler-processing-testing", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN])
@@ -1016,8 +1016,6 @@
 includeProject(":tv:tv-material", [BuildType.COMPOSE])
 includeProject(":tv:integration-tests:playground", [BuildType.COMPOSE])
 includeProject(":tv:integration-tests:presentation", [BuildType.COMPOSE])
-includeProject(":tv:integration-tests:macrobenchmark", [BuildType.COMPOSE])
-includeProject(":tv:integration-tests:macrobenchmark-target", [BuildType.COMPOSE])
 includeProject(":tv:tv-samples", "tv/samples", [BuildType.COMPOSE])
 includeProject(":tvprovider:tvprovider", [BuildType.MAIN])
 includeProject(":vectordrawable:integration-tests:testapp", [BuildType.MAIN])
@@ -1248,6 +1246,11 @@
     if (projectPath in included) return
     included.add(projectPath)
     for (reference in projectReferences[projectPath]) {
+        if (!allProjects.containsKey(reference) || allProjects[reference] == "") {
+            throw new GradleException("Project $reference does not exist.\n" +
+                    "Please check the build.gradle file for your $projectPath project " +
+                    "and update the project dependencies.")
+        }
         addReferences(reference, included)
     }
 }
diff --git a/slice/slice-builders/lint-baseline.xml b/slice/slice-builders/lint-baseline.xml
index 125de78..3679718 100644
--- a/slice/slice-builders/lint-baseline.xml
+++ b/slice/slice-builders/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-beta02" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta02)" variant="all" version="8.1.0-beta02">
+<issues format="6" by="lint 8.3.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
 
     <issue
         id="WrongConstant"
@@ -28,976 +28,4 @@
             file="src/main/java/androidx/slice/builders/impl/ListBuilderImpl.java"/>
     </issue>
 
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SliceAction getPrimaryAction() {"
-        errorLine2="           ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/GridRowBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public List&lt;CellBuilder> getCells() {"
-        errorLine2="           ~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/GridRowBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public CellBuilder getSeeMoreCell() {"
-        errorLine2="           ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/GridRowBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public PendingIntent getSeeMoreIntent() {"
-        errorLine2="           ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/GridRowBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public CharSequence getDescription() {"
-        errorLine2="           ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/GridRowBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public List&lt;Object> getObjects() {"
-        errorLine2="               ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/GridRowBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public List&lt;Integer> getTypes() {"
-        errorLine2="               ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/GridRowBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public List&lt;Boolean> getLoadings() {"
-        errorLine2="               ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/GridRowBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public CharSequence getCellDescription() {"
-        errorLine2="               ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/GridRowBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public PendingIntent getContentIntent() {"
-        errorLine2="               ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/GridRowBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public GridRowBuilderListV1Impl(@NonNull ListBuilderImpl parent, GridRowBuilder builder) {"
-        errorLine2="                                                                     ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/GridRowBuilderListV1Impl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void addCell(CellBuilder builder) {"
-        errorLine2="                        ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/GridRowBuilderListV1Impl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setSeeMoreAction(PendingIntent intent) {"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/GridRowBuilderListV1Impl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setPrimaryAction(SliceAction action) {"
-        errorLine2="                                 ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/GridRowBuilderListV1Impl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setContentDescription(CharSequence description) {"
-        errorLine2="                                      ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/GridRowBuilderListV1Impl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void fillFrom(CellBuilder builder) {"
-        errorLine2="                             ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/GridRowBuilderListV1Impl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public ListBuilderBasicImpl(Slice.Builder b, SliceSpec spec) {"
-        errorLine2="                                ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/ListBuilderBasicImpl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public ListBuilderBasicImpl(Slice.Builder b, SliceSpec spec) {"
-        errorLine2="                                                 ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/ListBuilderBasicImpl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public MessagingBasicImpl(Slice.Builder builder, SliceSpec spec) {"
-        errorLine2="                              ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/MessagingBasicImpl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public MessagingBasicImpl(Slice.Builder builder, SliceSpec spec) {"
-        errorLine2="                                                     ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/MessagingBasicImpl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void add(TemplateBuilderImpl builder) {"
-        errorLine2="                    ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/MessagingBasicImpl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public TemplateBuilderImpl createMessageBuilder() {"
-        errorLine2="           ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/MessagingBasicImpl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public MessageBuilder(MessagingBasicImpl parent) {"
-        errorLine2="                              ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/MessagingBasicImpl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void addSource(Icon source) {"
-        errorLine2="                              ~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/MessagingBasicImpl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void addText(CharSequence text) {"
-        errorLine2="                            ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/MessagingBasicImpl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    void add(TemplateBuilderImpl builder);"
-        errorLine2="             ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/MessagingBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    TemplateBuilderImpl createMessageBuilder();"
-        errorLine2="    ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/MessagingBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        void addSource(Icon source);"
-        errorLine2="                       ~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/MessagingBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        void addText(CharSequence text);"
-        errorLine2="                     ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/MessagingBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public MessagingListV1Impl(Slice.Builder b, SliceSpec spec) {"
-        errorLine2="                               ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/MessagingListV1Impl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public MessagingListV1Impl(Slice.Builder b, SliceSpec spec) {"
-        errorLine2="                                                ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/MessagingListV1Impl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void add(TemplateBuilderImpl builder) {"
-        errorLine2="                    ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/MessagingListV1Impl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public TemplateBuilderImpl createMessageBuilder() {"
-        errorLine2="           ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/MessagingListV1Impl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public MessageBuilder(MessagingListV1Impl parent) {"
-        errorLine2="                              ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/MessagingListV1Impl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void addSource(Icon source) {"
-        errorLine2="                              ~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/MessagingListV1Impl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void addText(CharSequence text) {"
-        errorLine2="                            ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/MessagingListV1Impl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public MessagingSliceBuilder add(MessageBuilder builder) {"
-        errorLine2="           ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/MessagingSliceBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public MessagingSliceBuilder add(MessageBuilder builder) {"
-        errorLine2="                                     ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/MessagingSliceBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public MessagingSliceBuilder add(Consumer&lt;MessageBuilder> c) {"
-        errorLine2="           ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/MessagingSliceBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public MessagingSliceBuilder add(Consumer&lt;MessageBuilder> c) {"
-        errorLine2="                                     ~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/MessagingSliceBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected TemplateBuilderImpl selectImpl() {"
-        errorLine2="              ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/MessagingSliceBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public MessageBuilder(MessagingSliceBuilder parent) {"
-        errorLine2="                              ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/MessagingSliceBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public MessageBuilder addSource(Icon source) {"
-        errorLine2="               ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/MessagingSliceBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public MessageBuilder addSource(Icon source) {"
-        errorLine2="                                        ~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/MessagingSliceBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public MessageBuilder addSource(IconCompat source) {"
-        errorLine2="               ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/MessagingSliceBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public MessageBuilder addSource(IconCompat source) {"
-        errorLine2="                                        ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/MessagingSliceBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public MessageBuilder addText(CharSequence text) {"
-        errorLine2="               ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/MessagingSliceBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public MessageBuilder addText(CharSequence text) {"
-        errorLine2="                                      ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/MessagingSliceBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public MessageBuilder addTimestamp(long timestamp) {"
-        errorLine2="               ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/MessagingSliceBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public MessagingV1Impl(Slice.Builder b, SliceSpec spec) {"
-        errorLine2="                           ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/MessagingV1Impl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public MessagingV1Impl(Slice.Builder b, SliceSpec spec) {"
-        errorLine2="                                            ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/MessagingV1Impl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void add(TemplateBuilderImpl builder) {"
-        errorLine2="                    ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/MessagingV1Impl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public TemplateBuilderImpl createMessageBuilder() {"
-        errorLine2="           ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/MessagingV1Impl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public MessageBuilder(MessagingV1Impl parent) {"
-        errorLine2="                              ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/MessagingV1Impl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void addSource(Icon source) {"
-        errorLine2="                              ~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/MessagingV1Impl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void addText(CharSequence text) {"
-        errorLine2="                            ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/MessagingV1Impl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SelectionBuilder addOption(String optionKey, CharSequence optionText) {"
-        errorLine2="           ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SelectionBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SelectionBuilder addOption(String optionKey, CharSequence optionText) {"
-        errorLine2="                                      ~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SelectionBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SelectionBuilder addOption(String optionKey, CharSequence optionText) {"
-        errorLine2="                                                        ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SelectionBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SelectionBuilder setPrimaryAction(@NonNull SliceAction primaryAction) {"
-        errorLine2="           ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SelectionBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SelectionBuilder setInputAction(@NonNull PendingIntent inputAction) {"
-        errorLine2="           ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SelectionBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SelectionBuilder setInputAction(@NonNull RemoteCallback inputAction) {"
-        errorLine2="           ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SelectionBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SelectionBuilder setSelectedOption(String selectedOption) {"
-        errorLine2="           ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SelectionBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SelectionBuilder setSelectedOption(String selectedOption) {"
-        errorLine2="                                              ~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SelectionBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SelectionBuilder setTitle(@Nullable CharSequence title) {"
-        errorLine2="           ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SelectionBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SelectionBuilder setSubtitle(@Nullable CharSequence subtitle) {"
-        errorLine2="           ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SelectionBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SelectionBuilder setContentDescription(@Nullable CharSequence contentDescription) {"
-        errorLine2="           ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SelectionBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SelectionBuilder setLayoutDirection("
-        errorLine2="           ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SelectionBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public List&lt;Pair&lt;String, CharSequence>> getOptions() {"
-        errorLine2="           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SelectionBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SliceAction getPrimaryAction() {"
-        errorLine2="           ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SelectionBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public PendingIntent getInputAction() {"
-        errorLine2="           ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SelectionBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public String getSelectedOption() {"
-        errorLine2="           ~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SelectionBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public CharSequence getTitle() {"
-        errorLine2="           ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SelectionBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public CharSequence getSubtitle() {"
-        errorLine2="           ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SelectionBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public CharSequence getContentDescription() {"
-        errorLine2="           ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SelectionBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SelectionBuilderBasicImpl(Slice.Builder sliceBuilder,"
-        errorLine2="                                     ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/SelectionBuilderBasicImpl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="                                     SelectionBuilder selectionBuilder) {"
-        errorLine2="                                     ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/SelectionBuilderBasicImpl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SelectionBuilderImpl(Slice.Builder sliceBuilder,"
-        errorLine2="                                ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/SelectionBuilderImpl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="                                SelectionBuilder selectionBuilder) {"
-        errorLine2="                                ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/SelectionBuilderImpl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected SelectionBuilder getSelectionBuilder() {"
-        errorLine2="              ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/SelectionBuilderImpl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SelectionBuilderListV2Impl(Slice.Builder parentSliceBuilder,"
-        errorLine2="                                      ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/SelectionBuilderListV2Impl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="                                  SelectionBuilder selectionBuilder) {"
-        errorLine2="                                  ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/SelectionBuilderListV2Impl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static SliceAction create(@NonNull PendingIntent action,"
-        errorLine2="                  ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SliceAction.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static SliceAction create(@NonNull RemoteCallback action,"
-        errorLine2="                  ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SliceAction.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static SliceAction createDeeplink(@NonNull PendingIntent action,"
-        errorLine2="                  ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SliceAction.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static SliceAction createDeeplink(@NonNull RemoteCallback action,"
-        errorLine2="                  ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SliceAction.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static SliceAction createToggle(@NonNull PendingIntent action,"
-        errorLine2="                  ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SliceAction.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static SliceAction createToggle(@NonNull RemoteCallback action,"
-        errorLine2="                  ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SliceAction.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static SliceAction createToggle(@NonNull PendingIntent action,"
-        errorLine2="                  ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SliceAction.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static SliceAction createToggle(@NonNull RemoteCallback action,"
-        errorLine2="                  ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/SliceAction.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected TemplateBuilderImpl(Slice.Builder b, SliceSpec spec) {"
-        errorLine2="                                  ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/TemplateBuilderImpl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected TemplateBuilderImpl(Slice.Builder b, SliceSpec spec) {"
-        errorLine2="                                                   ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/TemplateBuilderImpl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected TemplateBuilderImpl(Slice.Builder b, SliceSpec spec, Clock clock) {"
-        errorLine2="                                  ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/TemplateBuilderImpl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected TemplateBuilderImpl(Slice.Builder b, SliceSpec spec, Clock clock) {"
-        errorLine2="                                                   ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/TemplateBuilderImpl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected TemplateBuilderImpl(Slice.Builder b, SliceSpec spec, Clock clock) {"
-        errorLine2="                                                                   ~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/TemplateBuilderImpl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected void setBuilder(Slice.Builder builder) {"
-        errorLine2="                              ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/TemplateBuilderImpl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public Slice build() {"
-        errorLine2="           ~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/TemplateBuilderImpl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public Slice.Builder getBuilder() {"
-        errorLine2="           ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/TemplateBuilderImpl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public Slice.Builder createChildBuilder() {"
-        errorLine2="           ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/TemplateBuilderImpl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public Clock getClock() {"
-        errorLine2="           ~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/TemplateBuilderImpl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SliceSpec getSpec() {"
-        errorLine2="           ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/impl/TemplateBuilderImpl.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected TemplateSliceBuilder(TemplateBuilderImpl impl) {"
-        errorLine2="                                   ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/TemplateSliceBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public TemplateSliceBuilder(Context context, Uri uri) {"
-        errorLine2="                                ~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/TemplateSliceBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public TemplateSliceBuilder(Context context, Uri uri) {"
-        errorLine2="                                                 ~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/TemplateSliceBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected Slice.Builder getBuilder() {"
-        errorLine2="              ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/TemplateSliceBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected TemplateBuilderImpl selectImpl() {"
-        errorLine2="              ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/TemplateSliceBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected boolean checkCompatible(SliceSpec candidate) {"
-        errorLine2="                                      ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/TemplateSliceBuilder.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected Clock getClock() {"
-        errorLine2="              ~~~~~">
-        <location
-            file="src/main/java/androidx/slice/builders/TemplateSliceBuilder.java"/>
-    </issue>
-
 </issues>
diff --git a/slice/slice-core/lint-baseline.xml b/slice/slice-core/lint-baseline.xml
index f6b771e..4b5f8f1 100644
--- a/slice/slice-core/lint-baseline.xml
+++ b/slice/slice-core/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-beta02" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta02)" variant="all" version="8.1.0-beta02">
+<issues format="6" by="lint 8.3.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
 
     <issue
         id="BanUncheckedReflection"
@@ -10,85 +10,4 @@
             file="src/main/java/androidx/slice/SliceManagerWrapper.java"/>
     </issue>
 
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public SliceProviderWrapper(androidx.slice.SliceProvider provider,"
-        errorLine2="                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/compat/SliceProviderWrapperContainer.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="                String[] autoGrantPermissions) {"
-        errorLine2="                ~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/compat/SliceProviderWrapperContainer.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public PendingIntent onCreatePermissionRequest(Uri sliceUri) {"
-        errorLine2="               ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/compat/SliceProviderWrapperContainer.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public Bundle call(String method, String arg, Bundle extras) {"
-        errorLine2="               ~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/compat/SliceProviderWrapperContainer.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public Bundle call(String method, String arg, Bundle extras) {"
-        errorLine2="                           ~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/compat/SliceProviderWrapperContainer.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public Bundle call(String method, String arg, Bundle extras) {"
-        errorLine2="                                          ~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/compat/SliceProviderWrapperContainer.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public Bundle call(String method, String arg, Bundle extras) {"
-        errorLine2="                                                      ~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/compat/SliceProviderWrapperContainer.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public Collection&lt;Uri> onGetSliceDescendants(Uri uri) {"
-        errorLine2="               ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/compat/SliceProviderWrapperContainer.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public Collection&lt;Uri> onGetSliceDescendants(Uri uri) {"
-        errorLine2="                                                     ~~~">
-        <location
-            file="src/main/java/androidx/slice/compat/SliceProviderWrapperContainer.java"/>
-    </issue>
-
 </issues>
diff --git a/slice/slice-view/lint-baseline.xml b/slice/slice-view/lint-baseline.xml
index d89abc3..688d705 100644
--- a/slice/slice-view/lint-baseline.xml
+++ b/slice/slice-view/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-beta02" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta02)" variant="all" version="8.1.0-beta02">
+<issues format="6" by="lint 8.3.0-alpha02" type="baseline" client="cli" dependencies="false" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
 
     <issue
         id="BanSynchronizedMethods"
@@ -73,1345 +73,4 @@
             file="src/main/java/androidx/slice/widget/TemplateView.java"/>
     </issue>
 
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public ActionRow(Context context, boolean fullActions) {"
-        errorLine2="                     ~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/ActionRow.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public int getHeight(SliceStyle style, SliceViewPolicy policy) {"
-        errorLine2="                         ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/ListContent.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public int getHeight(SliceStyle style, SliceViewPolicy policy) {"
-        errorLine2="                                           ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/ListContent.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public DisplayedListItems getRowItems(int availableHeight, SliceStyle style,"
-        errorLine2="           ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/ListContent.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public DisplayedListItems getRowItems(int availableHeight, SliceStyle style,"
-        errorLine2="                                                               ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/ListContent.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="            SliceViewPolicy policy) {"
-        errorLine2="            ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/ListContent.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SliceContent getSeeMoreItem() {"
-        errorLine2="           ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/ListContent.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static int getRowType(SliceContent content, boolean isHeader,"
-        errorLine2="                                 ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/ListContent.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="                                 List&lt;SliceAction> actions) {"
-        errorLine2="                                 ~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/ListContent.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static int getListHeight(List&lt;SliceContent> listItems, SliceStyle style,"
-        errorLine2="                                    ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/ListContent.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static int getListHeight(List&lt;SliceContent> listItems, SliceStyle style,"
-        errorLine2="                                                                  ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/ListContent.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="            SliceViewPolicy policy) {"
-        errorLine2="            ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/ListContent.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static void trackInputFocused(ViewGroup parent) {"
-        errorLine2="                                         ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/LocationBasedViewTracker.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static void trackA11yFocus(ViewGroup parent) {"
-        errorLine2="                                      ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/LocationBasedViewTracker.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public MessageView(Context context) {"
-        errorLine2="                       ~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/MessageView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setSliceItem(SliceContent content, boolean isHeader, int index,"
-        errorLine2="                             ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/MessageView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="            int rowCount, SliceView.OnSliceActionListener observer) {"
-        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/MessageView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public RemoteInputView(Context context, AttributeSet attrs) {"
-        errorLine2="                           ~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/RemoteInputView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public RemoteInputView(Context context, AttributeSet attrs) {"
-        errorLine2="                                            ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/RemoteInputView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static RemoteInputView inflate(Context context, ViewGroup root) {"
-        errorLine2="                  ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/RemoteInputView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static RemoteInputView inflate(Context context, ViewGroup root) {"
-        errorLine2="                                          ~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/RemoteInputView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static RemoteInputView inflate(Context context, ViewGroup root) {"
-        errorLine2="                                                           ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/RemoteInputView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setAction(SliceItem action) {"
-        errorLine2="                          ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/RemoteInputView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput) {"
-        errorLine2="                               ~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/RemoteInputView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput) {"
-        errorLine2="                                                           ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/RemoteInputView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public RemoteEditText(Context context, AttributeSet attrs) {"
-        errorLine2="                              ~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/RemoteInputView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public RemoteEditText(Context context, AttributeSet attrs) {"
-        errorLine2="                                               ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/RemoteInputView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        protected void onVisibilityChanged(View changedView, int visibility) {"
-        errorLine2="                                           ~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/RemoteInputView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {"
-        errorLine2="                                                                      ~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/RemoteInputView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public RowContent(SliceItem rowSlice, int position) {"
-        errorLine2="                      ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/RowContent.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public List&lt;SliceItem> getEndItems() {"
-        errorLine2="           ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/RowContent.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public List&lt;SliceAction> getToggleItems() {"
-        errorLine2="           ~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/RowContent.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public int getHeight(SliceStyle style, SliceViewPolicy policy) {"
-        errorLine2="                         ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/RowContent.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public int getHeight(SliceStyle style, SliceViewPolicy policy) {"
-        errorLine2="                                           ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/RowContent.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public RowStyle(Context context, int resId, @NonNull SliceStyle sliceStyle) {"
-        errorLine2="                    ~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/RowStyle.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected Set&lt;SliceItem> mLoadingActions = new HashSet&lt;>();"
-        errorLine2="              ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/RowView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setStyle(SliceStyle styles, RowStyle rowStyle) {"
-        errorLine2="                         ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/RowView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setStyle(SliceStyle styles, RowStyle rowStyle) {"
-        errorLine2="                                            ~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/RowView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setSliceActions(List&lt;SliceAction> actions) {"
-        errorLine2="                                ~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/RowView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setLoadingActions(Set&lt;SliceItem> actions) {"
-        errorLine2="                                  ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/RowView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public ShortcutView(Context context) {"
-        errorLine2="                        ~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/ShortcutView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setSliceContent(ListContent sliceContent) {"
-        errorLine2="                                ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/ShortcutView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setLoadingActions(Set&lt;SliceItem> actions) {"
-        errorLine2="                                  ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/ShortcutView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public Set&lt;SliceItem> getLoadingActions() {"
-        errorLine2="           ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/ShortcutView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SliceActionView(Context context, SliceStyle style, RowStyle rowStyle) {"
-        errorLine2="                           ~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceActionView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SliceActionView(Context context, SliceStyle style, RowStyle rowStyle) {"
-        errorLine2="                                            ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceActionView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SliceActionView(Context context, SliceStyle style, RowStyle rowStyle) {"
-        errorLine2="                                                              ~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceActionView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setAction(@NonNull SliceActionImpl action, EventInfo info,"
-        errorLine2="                                                           ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceActionView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="            SliceView.OnSliceActionListener listener, int color,"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceActionView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="            SliceActionLoadingListener loadingListener) {"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceActionView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setParents(SliceView parent, TemplateView templateView) {"
-        errorLine2="                           ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceAdapter.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setParents(SliceView parent, TemplateView templateView) {"
-        errorLine2="                                             ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceAdapter.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setSliceObserver(SliceView.OnSliceActionListener observer) {"
-        errorLine2="                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceAdapter.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setSliceActions(List&lt;SliceAction> actions) {"
-        errorLine2="                                ~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceAdapter.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setSliceItems(List&lt;SliceContent> slices, int color, int mode) {"
-        errorLine2="                              ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceAdapter.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setStyle(SliceStyle style) {"
-        errorLine2="                         ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceAdapter.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setPolicy(SliceViewPolicy p) {"
-        errorLine2="                          ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceAdapter.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setLoadingActions(Set&lt;SliceItem> actions) {"
-        errorLine2="                                  ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceAdapter.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public Set&lt;SliceItem> getLoadingActions() {"
-        errorLine2="           ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceAdapter.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void onSliceActionLoading(SliceItem actionItem, int position) {"
-        errorLine2="                                     ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceAdapter.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public SliceViewHolder(View itemView) {"
-        errorLine2="                               ~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceAdapter.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected SliceView.OnSliceActionListener mObserver;"
-        errorLine2="              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceChildView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected SliceActionView.SliceActionLoadingListener mLoadingListener;"
-        errorLine2="              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceChildView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected SliceStyle mSliceStyle;"
-        errorLine2="              ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceChildView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected RowStyle mRowStyle;"
-        errorLine2="              ~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceChildView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected SliceViewPolicy mViewPolicy;"
-        errorLine2="              ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceChildView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setSliceContent(ListContent content) {"
-        errorLine2="                                ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceChildView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setSliceActions(List&lt;SliceAction> actions) {"
-        errorLine2="                                ~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceChildView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setSliceActionListener(SliceView.OnSliceActionListener observer) {"
-        errorLine2="                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceChildView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setSliceActionLoadingListener(SliceActionView.SliceActionLoadingListener listener) {"
-        errorLine2="                                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceChildView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setActionLoading(SliceItem item) {"
-        errorLine2="                                 ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceChildView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setLoadingActions(Set&lt;SliceItem> loadingActions) {"
-        errorLine2="                                  ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceChildView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public Set&lt;SliceItem> getLoadingActions() {"
-        errorLine2="           ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceChildView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setStyle(SliceStyle styles, @NonNull RowStyle rowStyle) {"
-        errorLine2="                         ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceChildView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected SliceItem mSliceItem;"
-        errorLine2="              ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceContent.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected SliceItem mColorItem;"
-        errorLine2="              ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceContent.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected SliceItem mLayoutDirItem;"
-        errorLine2="              ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceContent.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected SliceItem mContentDescr;"
-        errorLine2="              ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceContent.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public int getHeight(SliceStyle style, SliceViewPolicy policy) {"
-        errorLine2="                         ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceContent.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public int getHeight(SliceStyle style, SliceViewPolicy policy) {"
-        errorLine2="                                           ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceContent.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="            @NonNull InputStream input, OnErrorListener listener) {"
-        errorLine2="                                        ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceLiveData.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="            @NonNull InputStream input, OnErrorListener listener) {"
-        errorLine2="                                        ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceLiveData.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="            SliceViewManager manager, @NonNull InputStream input, OnErrorListener listener) {"
-        errorLine2="            ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceLiveData.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="            SliceViewManager manager, @NonNull InputStream input, OnErrorListener listener) {"
-        errorLine2="                                                                  ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceLiveData.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public List&lt;SliceAction> getToggles() {"
-        errorLine2="           ~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/SliceMetadata.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public boolean sendToggleAction(SliceAction toggleAction, boolean toggleValue)"
-        errorLine2="                                    ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/SliceMetadata.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public ListContent getListContent() {"
-        errorLine2="           ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/SliceMetadata.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SliceStructure(Slice s) {"
-        errorLine2="                          ~~~~~">
-        <location
-            file="src/main/java/androidx/slice/SliceStructure.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SliceStructure(SliceItem s) {"
-        errorLine2="                          ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/SliceStructure.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SliceStyle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {"
-        errorLine2="                      ~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceStyle.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SliceStyle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {"
-        errorLine2="                                       ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceStyle.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public int getRowHeight(RowContent row, SliceViewPolicy policy) {"
-        errorLine2="                            ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceStyle.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public int getRowHeight(RowContent row, SliceViewPolicy policy) {"
-        errorLine2="                                            ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceStyle.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public int getGridHeight(GridContent grid, SliceViewPolicy policy) {"
-        errorLine2="                             ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceStyle.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public int getGridHeight(GridContent grid, SliceViewPolicy policy) {"
-        errorLine2="                                               ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceStyle.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public int getListHeight(ListContent list, SliceViewPolicy policy) {"
-        errorLine2="                             ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceStyle.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public int getListHeight(ListContent list, SliceViewPolicy policy) {"
-        errorLine2="                                               ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceStyle.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public int getListItemsHeight(List&lt;SliceContent> listItems, SliceViewPolicy policy) {"
-        errorLine2="                                  ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceStyle.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public int getListItemsHeight(List&lt;SliceContent> listItems, SliceViewPolicy policy) {"
-        errorLine2="                                                                ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceStyle.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public DisplayedListItems getListItemsForNonScrollingList(ListContent list,"
-        errorLine2="                                                              ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceStyle.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="                                                             SliceViewPolicy policy) {"
-        errorLine2="                                                             ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceStyle.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static IconCompat convert(Context context, IconCompat icon, SerializeOptions options) {"
-        errorLine2="                  ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/SliceUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static IconCompat convert(Context context, IconCompat icon, SerializeOptions options) {"
-        errorLine2="                                     ~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/SliceUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static IconCompat convert(Context context, IconCompat icon, SerializeOptions options) {"
-        errorLine2="                                                      ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/SliceUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static IconCompat convert(Context context, IconCompat icon, SerializeOptions options) {"
-        errorLine2="                                                                       ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/SliceUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public void checkThrow(String format) {"
-        errorLine2="                               ~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/SliceUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public Bitmap.CompressFormat getFormat() {"
-        errorLine2="               ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/SliceUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public SerializeOptions setActionMode(@FormatMode int mode) {"
-        errorLine2="               ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/SliceUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public SerializeOptions setImageMode(@FormatMode int mode) {"
-        errorLine2="               ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/SliceUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public SerializeOptions setMaxImageWidth(int width) {"
-        errorLine2="               ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/SliceUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public SerializeOptions setMaxImageHeight(int height) {"
-        errorLine2="               ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/SliceUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public SerializeOptions setImageConversionFormat(Bitmap.CompressFormat format,"
-        errorLine2="               ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/SliceUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public SerializeOptions setImageConversionFormat(Bitmap.CompressFormat format,"
-        errorLine2="                                                         ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/SliceUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        void onSliceAction(Uri actionUri, Context context, Intent intent);"
-        errorLine2="                           ~~~">
-        <location
-            file="src/main/java/androidx/slice/SliceUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        void onSliceAction(Uri actionUri, Context context, Intent intent);"
-        errorLine2="                                          ~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/SliceUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        void onSliceAction(Uri actionUri, Context context, Intent intent);"
-        errorLine2="                                                           ~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/SliceUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public SliceParseException(String s, Throwable e) {"
-        errorLine2="                                   ~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/SliceUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public SliceParseException(String s, Throwable e) {"
-        errorLine2="                                             ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/SliceUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="        public SliceParseException(String s) {"
-        errorLine2="                                   ~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/SliceUtils.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SliceView(Context context) {"
-        errorLine2="                     ~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SliceView(Context context, @Nullable AttributeSet attrs) {"
-        errorLine2="                     ~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SliceView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {"
-        errorLine2="                     ~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SliceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {"
-        errorLine2="                     ~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public SliceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {"
-        errorLine2="                                      ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setClickInfo(int[] info) {"
-        errorLine2="                             ~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setOnClickListener(View.OnClickListener listener) {"
-        errorLine2="                                   ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setOnLongClickListener(View.OnLongClickListener listener) {"
-        errorLine2="                                       ~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static String modeToString(@SliceMode int mode) {"
-        errorLine2="                  ~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    protected void onVisibilityChanged(View changedView, int visibility) {"
-        errorLine2="                                       ~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setListener(PolicyChangeListener listener) {"
-        errorLine2="                            ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceViewPolicy.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static Drawable getDrawable(@NonNull Context context, @AttrRes int attr) {"
-        errorLine2="                  ~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceViewUtil.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static IconCompat createIconFromDrawable(Drawable d) {"
-        errorLine2="                  ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceViewUtil.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static IconCompat createIconFromDrawable(Drawable d) {"
-        errorLine2="                                                    ~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceViewUtil.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="            IconCompat icon, boolean isLarge, ViewGroup parent) {"
-        errorLine2="            ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceViewUtil.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="            IconCompat icon, boolean isLarge, ViewGroup parent) {"
-        errorLine2="                                              ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceViewUtil.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static @NonNull Bitmap getCircularBitmap(Bitmap bitmap) {"
-        errorLine2="                                                    ~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceViewUtil.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static CharSequence getTimestampString(Context context, long time) {"
-        errorLine2="                  ~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceViewUtil.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static CharSequence getTimestampString(Context context, long time) {"
-        errorLine2="                                                  ~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceViewUtil.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static void tintIndeterminateProgressBar(Context context, ProgressBar bar) {"
-        errorLine2="                                                    ~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceViewUtil.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public static void tintIndeterminateProgressBar(Context context, ProgressBar bar) {"
-        errorLine2="                                                                     ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/SliceViewUtil.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void onForegroundActivated(MotionEvent event) {"
-        errorLine2="                                      ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/TemplateView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setPolicy(SliceViewPolicy policy) {"
-        errorLine2="                          ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/TemplateView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setActionLoading(SliceItem item) {"
-        errorLine2="                                 ~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/TemplateView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setLoadingActions(Set&lt;SliceItem> loadingActions) {"
-        errorLine2="                                  ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/TemplateView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public Set&lt;SliceItem> getLoadingActions() {"
-        errorLine2="           ~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/TemplateView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setSliceActionListener(SliceView.OnSliceActionListener observer) {"
-        errorLine2="                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/TemplateView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setSliceActions(List&lt;SliceAction> actions) {"
-        errorLine2="                                ~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/TemplateView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setSliceContent(ListContent sliceContent) {"
-        errorLine2="                                ~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/TemplateView.java"/>
-    </issue>
-
-    <issue
-        id="UnknownNullness"
-        message="Unknown nullability; explicitly declare as `@Nullable` or `@NonNull` to improve Kotlin interoperability; see https://developer.android.com/kotlin/interop#nullability_annotations"
-        errorLine1="    public void setStyle(SliceStyle style, @NonNull RowStyle rowStyle) {"
-        errorLine2="                         ~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/slice/widget/TemplateView.java"/>
-    </issue>
-
 </issues>
diff --git a/transition/transition/src/main/java/androidx/transition/FragmentTransitionSupport.java b/transition/transition/src/main/java/androidx/transition/FragmentTransitionSupport.java
index 6a0d6c9..52dacee 100644
--- a/transition/transition/src/main/java/androidx/transition/FragmentTransitionSupport.java
+++ b/transition/transition/src/main/java/androidx/transition/FragmentTransitionSupport.java
@@ -229,6 +229,11 @@
     }
 
     @Override
+    public boolean isSeekingSupported() {
+        return true;
+    }
+
+    @Override
     public boolean isSeekingSupported(@NonNull Object transition) {
         boolean supported = ((Transition) transition).isSeekingSupported();
         if (!supported) {
diff --git a/tv/integration-tests/macrobenchmark-target/build.gradle b/tv/integration-tests/macrobenchmark-target/build.gradle
deleted file mode 100644
index df1edfa..0000000
--- a/tv/integration-tests/macrobenchmark-target/build.gradle
+++ /dev/null
@@ -1,33 +0,0 @@
-plugins {
-    id("AndroidXPlugin")
-    id("com.android.application")
-    id("AndroidXComposePlugin")
-    id("org.jetbrains.kotlin.android")
-}
-
-android {
-    namespace 'androidx.tv.integration.macrobenchmark.target'
-
-    defaultConfig {
-        minSdkVersion 28
-    }
-
-    buildTypes {
-        release {
-            minifyEnabled true
-            shrinkResources true
-            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
-                    'proguard-rules.pro'
-        }
-        benchmark {
-            initWith release
-            signingConfig signingConfigs.debug
-            matchingFallbacks = ['release']
-            debuggable false
-        }
-    }
-}
-
-dependencies {
-    implementation(libs.kotlinStdlib)
-}
\ No newline at end of file
diff --git a/tv/integration-tests/macrobenchmark-target/proguard-rules.pro b/tv/integration-tests/macrobenchmark-target/proguard-rules.pro
deleted file mode 100644
index 481bb43..0000000
--- a/tv/integration-tests/macrobenchmark-target/proguard-rules.pro
+++ /dev/null
@@ -1,21 +0,0 @@
-# Add project specific ProGuard rules here.
-# You can control the set of applied configuration files using the
-# proguardFiles setting in build.gradle.
-#
-# For more details, see
-#   http://developer.android.com/guide/developing/tools/proguard.html
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-#   public *;
-#}
-
-# Uncomment this to preserve the line number information for
-# debugging stack traces.
-#-keepattributes SourceFile,LineNumberTable
-
-# If you keep the line number information, uncomment this to
-# hide the original source file name.
-#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/tv/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml b/tv/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
deleted file mode 100644
index dffaec2..0000000
--- a/tv/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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.
-  -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools">
-
-    <application
-        android:allowBackup="false"
-        android:label="@string/app_name"
-        android:supportsRtl="true"
-        android:theme="@android:style/Theme.DeviceDefault"
-        tools:ignore="MissingApplicationIcon" />
-
-</manifest>
\ No newline at end of file
diff --git a/tv/integration-tests/macrobenchmark-target/src/main/res/drawable/ic_launcher_background.xml b/tv/integration-tests/macrobenchmark-target/src/main/res/drawable/ic_launcher_background.xml
deleted file mode 100644
index 8e2e905..0000000
--- a/tv/integration-tests/macrobenchmark-target/src/main/res/drawable/ic_launcher_background.xml
+++ /dev/null
@@ -1,186 +0,0 @@
-<?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.
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="108dp"
-    android:height="108dp"
-    android:viewportHeight="108"
-    android:viewportWidth="108">
-    <path
-        android:fillColor="#3DDC84"
-        android:pathData="M0,0h108v108h-108z" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M9,0L9,108"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,0L19,108"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M29,0L29,108"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M39,0L39,108"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M49,0L49,108"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M59,0L59,108"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M69,0L69,108"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M79,0L79,108"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M89,0L89,108"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M99,0L99,108"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,9L108,9"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,19L108,19"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,29L108,29"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,39L108,39"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,49L108,49"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,59L108,59"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,69L108,69"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,79L108,79"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,89L108,89"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M0,99L108,99"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,29L89,29"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,39L89,39"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,49L89,49"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,59L89,59"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,69L89,69"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M19,79L89,79"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M29,19L29,89"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M39,19L39,89"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M49,19L49,89"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M59,19L59,89"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M69,19L69,89"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-    <path
-        android:fillColor="#00000000"
-        android:pathData="M79,19L79,89"
-        android:strokeColor="#33FFFFFF"
-        android:strokeWidth="0.8" />
-</vector>
diff --git a/tv/integration-tests/macrobenchmark-target/src/main/res/drawable/ic_launcher_foreground.xml b/tv/integration-tests/macrobenchmark-target/src/main/res/drawable/ic_launcher_foreground.xml
deleted file mode 100644
index 6cadbfb..0000000
--- a/tv/integration-tests/macrobenchmark-target/src/main/res/drawable/ic_launcher_foreground.xml
+++ /dev/null
@@ -1,46 +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.
-  -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:aapt="http://schemas.android.com/aapt"
-    android:width="108dp"
-    android:height="108dp"
-    android:viewportHeight="108"
-    android:viewportWidth="108">
-    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
-        <aapt:attr name="android:fillColor">
-            <gradient
-                android:endX="85.84757"
-                android:endY="92.4963"
-                android:startX="42.9492"
-                android:startY="49.59793"
-                android:type="linear">
-                <item
-                    android:color="#44000000"
-                    android:offset="0.0" />
-                <item
-                    android:color="#00000000"
-                    android:offset="1.0" />
-            </gradient>
-        </aapt:attr>
-    </path>
-    <path
-        android:fillColor="#FFFFFF"
-        android:fillType="nonZero"
-        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
-        android:strokeColor="#00000000"
-        android:strokeWidth="1" />
-</vector>
\ No newline at end of file
diff --git a/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-anydpi/ic_launcher.xml b/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-anydpi/ic_launcher.xml
deleted file mode 100644
index 7528704..0000000
--- a/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-anydpi/ic_launcher.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?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.
-  -->
-
-<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
-    <background android:drawable="@drawable/ic_launcher_background" />
-    <foreground android:drawable="@drawable/ic_launcher_foreground" />
-    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
-</adaptive-icon>
\ No newline at end of file
diff --git a/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-anydpi/ic_launcher_round.xml
deleted file mode 100644
index 7528704..0000000
--- a/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-anydpi/ic_launcher_round.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?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.
-  -->
-
-<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
-    <background android:drawable="@drawable/ic_launcher_background" />
-    <foreground android:drawable="@drawable/ic_launcher_foreground" />
-    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
-</adaptive-icon>
\ No newline at end of file
diff --git a/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-hdpi/ic_launcher.webp b/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-hdpi/ic_launcher.webp
deleted file mode 100644
index c209e78..0000000
--- a/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-hdpi/ic_launcher.webp
+++ /dev/null
Binary files differ
diff --git a/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-hdpi/ic_launcher_round.webp
deleted file mode 100644
index b2dfe3d..0000000
--- a/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-hdpi/ic_launcher_round.webp
+++ /dev/null
Binary files differ
diff --git a/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-mdpi/ic_launcher.webp b/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-mdpi/ic_launcher.webp
deleted file mode 100644
index 4f0f1d6..0000000
--- a/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-mdpi/ic_launcher.webp
+++ /dev/null
Binary files differ
diff --git a/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-mdpi/ic_launcher_round.webp
deleted file mode 100644
index 62b611d..0000000
--- a/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-mdpi/ic_launcher_round.webp
+++ /dev/null
Binary files differ
diff --git a/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-xhdpi/ic_launcher.webp b/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-xhdpi/ic_launcher.webp
deleted file mode 100644
index 948a307..0000000
--- a/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-xhdpi/ic_launcher.webp
+++ /dev/null
Binary files differ
diff --git a/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
deleted file mode 100644
index 1b9a695..0000000
--- a/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
+++ /dev/null
Binary files differ
diff --git a/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-xxhdpi/ic_launcher.webp
deleted file mode 100644
index 28d4b77..0000000
--- a/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-xxhdpi/ic_launcher.webp
+++ /dev/null
Binary files differ
diff --git a/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
deleted file mode 100644
index 9287f50..0000000
--- a/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
+++ /dev/null
Binary files differ
diff --git a/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
deleted file mode 100644
index aa7d642..0000000
--- a/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
+++ /dev/null
Binary files differ
diff --git a/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
deleted file mode 100644
index 9126ae3..0000000
--- a/tv/integration-tests/macrobenchmark-target/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
+++ /dev/null
Binary files differ
diff --git a/tv/integration-tests/macrobenchmark-target/src/main/res/values/colors.xml b/tv/integration-tests/macrobenchmark-target/src/main/res/values/colors.xml
deleted file mode 100644
index ad6c63df..0000000
--- a/tv/integration-tests/macrobenchmark-target/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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>
-    <color name="purple_200">#FFBB86FC</color>
-    <color name="purple_500">#FF6200EE</color>
-    <color name="purple_700">#FF3700B3</color>
-    <color name="teal_200">#FF03DAC5</color>
-    <color name="teal_700">#FF018786</color>
-    <color name="black">#FF000000</color>
-    <color name="white">#FFFFFFFF</color>
-</resources>
\ No newline at end of file
diff --git a/tv/integration-tests/macrobenchmark-target/src/main/res/values/strings.xml b/tv/integration-tests/macrobenchmark-target/src/main/res/values/strings.xml
deleted file mode 100644
index a37f548..0000000
--- a/tv/integration-tests/macrobenchmark-target/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<resources>
-    <string name="app_name">TV Compose Macrobenchmark Target</string>
-</resources>
\ No newline at end of file
diff --git a/tv/integration-tests/macrobenchmark/build.gradle b/tv/integration-tests/macrobenchmark/build.gradle
deleted file mode 100644
index e1d220a..0000000
--- a/tv/integration-tests/macrobenchmark/build.gradle
+++ /dev/null
@@ -1,44 +0,0 @@
-plugins {
-    id("AndroidXPlugin")
-    id("com.android.test")
-    id("kotlin-android")
-}
-
-android {
-    namespace 'androidx.tv.integration.macrobenchmark'
-
-    defaultConfig {
-        minSdkVersion 24
-    }
-
-    buildTypes {
-        // This benchmark buildType is used for benchmarking, and should function like your
-        // release build (for example, with minification on). It's signed with a debug key
-        // for easy local/CI testing.
-        benchmark {
-            debuggable = true
-            signingConfig = debug.signingConfig
-            matchingFallbacks = ["release"]
-        }
-    }
-
-    targetProjectPath = ":tv:integration-tests:macrobenchmark-target"
-    experimentalProperties["android.experimental.self-instrumenting"] = true
-}
-
-dependencies {
-    implementation(project(":benchmark:benchmark-junit4"))
-    implementation(project(":benchmark:benchmark-macro-junit4"))
-    implementation(project(":internal-testutils-macrobenchmark"))
-    implementation(libs.testRules)
-    implementation(libs.testExtJunit)
-    implementation(libs.testCore)
-    implementation(libs.testRunner)
-    implementation(libs.testUiautomator)
-}
-
-androidComponents {
-    beforeVariants(selector().all()) {
-        enable = buildType == "benchmark"
-    }
-}
\ No newline at end of file
diff --git a/tv/integration-tests/macrobenchmark/src/main/AndroidManifest.xml b/tv/integration-tests/macrobenchmark/src/main/AndroidManifest.xml
deleted file mode 100644
index 227314e..0000000
--- a/tv/integration-tests/macrobenchmark/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1 +0,0 @@
-<manifest />
\ No newline at end of file
diff --git a/tv/integration-tests/macrobenchmark/src/main/java/androidx/tv/integration/macrobenchmark/ExampleStartupBenchmark.kt b/tv/integration-tests/macrobenchmark/src/main/java/androidx/tv/integration/macrobenchmark/ExampleStartupBenchmark.kt
deleted file mode 100644
index 821b725..0000000
--- a/tv/integration-tests/macrobenchmark/src/main/java/androidx/tv/integration/macrobenchmark/ExampleStartupBenchmark.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-package androidx.tv.integration.macrobenchmark
-
-import androidx.benchmark.macro.StartupMode
-import androidx.benchmark.macro.StartupTimingMetric
-import androidx.benchmark.macro.junit4.MacrobenchmarkRule
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-/**
- * This is an example startup benchmark.
- *
- * It navigates to the device's home screen, and launches the default activity.
- *
- * Before running this benchmark:
- * 1) switch your app's active build variant in the Studio (affects Studio runs only)
- * 2) add `<profileable android:shell="true" />` to your app's manifest, within the `<application>` tag
- *
- * Run this benchmark from Studio to see startup measurements, and captured system traces
- * for investigating your app's performance.
- */
-@RunWith(AndroidJUnit4::class)
-class ExampleStartupBenchmark {
-    @get:Rule
-    val benchmarkRule = MacrobenchmarkRule()
-
-    @Test
-    fun startup() = benchmarkRule.measureRepeated(
-        packageName = "androidx.tv.integration.macrobenchmark.target",
-        metrics = listOf(StartupTimingMetric()),
-        iterations = 5,
-        startupMode = StartupMode.COLD
-    ) {
-        pressHome()
-        startActivityAndWait()
-    }
-}
diff --git a/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/FeaturedCarousel.kt b/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/FeaturedCarousel.kt
index ac7fe19..85c9d28 100644
--- a/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/FeaturedCarousel.kt
+++ b/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/FeaturedCarousel.kt
@@ -28,6 +28,7 @@
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.clickable
+import androidx.compose.foundation.focusGroup
 import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
@@ -45,6 +46,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.key
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
@@ -54,6 +56,7 @@
 import androidx.compose.ui.focus.FocusDirection
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.focus.focusRestorer
 import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.onPlaced
@@ -69,37 +72,48 @@
 import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.rememberCarouselState
 
+@OptIn(ExperimentalComposeUiApi::class)
 @Composable
 fun FeaturedCarouselContent() {
     LazyColumn(verticalArrangement = Arrangement.spacedBy(20.dp)) {
         items(3) { SampleLazyRow() }
         item {
             Row(horizontalArrangement = Arrangement.spacedBy(20.dp)) {
-                Column(verticalArrangement = Arrangement.spacedBy(20.dp)) {
+                Column(
+                    modifier = Modifier.focusRestorer().focusGroup(),
+                    verticalArrangement = Arrangement.spacedBy(20.dp)
+                ) {
                     repeat(3) {
-                        Box(
-                            modifier = Modifier
-                                .background(Color.Magenta.copy(alpha = 0.3f))
-                                .width(50.dp)
-                                .height(50.dp)
-                                .drawBorderOnFocus()
-                                .focusable()
-                        )
+                        key(it) {
+                            Box(
+                                modifier = Modifier
+                                    .background(Color.Magenta.copy(alpha = 0.3f))
+                                    .width(50.dp)
+                                    .height(50.dp)
+                                    .drawBorderOnFocus()
+                                    .focusable()
+                            )
+                        }
                     }
                 }
 
                 FeaturedCarousel(Modifier.weight(1f))
 
-                Column(verticalArrangement = Arrangement.spacedBy(20.dp)) {
+                Column(
+                    modifier = Modifier.focusRestorer().focusGroup(),
+                    verticalArrangement = Arrangement.spacedBy(20.dp)
+                ) {
                     repeat(3) {
-                        Box(
-                            modifier = Modifier
-                                .background(Color.Magenta.copy(alpha = 0.3f))
-                                .width(50.dp)
-                                .height(50.dp)
-                                .drawBorderOnFocus()
-                                .focusable()
-                        )
+                        key(it) {
+                            Box(
+                                modifier = Modifier
+                                    .background(Color.Magenta.copy(alpha = 0.3f))
+                                    .width(50.dp)
+                                    .height(50.dp)
+                                    .drawBorderOnFocus()
+                                    .focusable()
+                            )
+                        }
                     }
                 }
             }
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 c9d1f31..a7c3da3 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
@@ -38,8 +38,10 @@
 import androidx.compose.runtime.remember
 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.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.tv.material3.ExperimentalTvMaterial3Api
@@ -75,9 +77,15 @@
         Row(Modifier.fillMaxSize()) {
             Box(modifier = Modifier.height(400.dp)) {
                 androidx.tv.material3.ModalNavigationDrawer(
-                    drawerContent = { Sidebar(direction = direction) }
+                    drawerContent = { Sidebar(direction = direction) },
+                    scrimBrush = Brush.verticalGradient(
+                        listOf(
+                            Color.DarkGray.copy(alpha = 0.2f),
+                            Color.LightGray.copy(alpha = 0.2f)
+                        )
+                    )
                 ) {
-                    CommonBackground()
+                    CommonBackground(startPadding = 90.dp)
                 }
             }
         }
@@ -85,9 +93,13 @@
 }
 
 @Composable
-private fun CommonBackground() {
-    Row(modifier = Modifier.padding(start = 10.dp)) {
-        Card(backgroundColor = Color.Red)
+private fun CommonBackground(startPadding: Dp = 10.dp) {
+    Box(modifier = Modifier
+        .fillMaxSize()
+        .background(Color.Blue.copy(alpha = 0.3f))) {
+        Row(modifier = Modifier.padding(start = startPadding)) {
+            Card(backgroundColor = Color.Red)
+        }
     }
 }
 
@@ -106,7 +118,7 @@
     Column(
         modifier = Modifier
             .fillMaxHeight()
-            .background(pageColor)
+            .background(pageColor.copy(alpha = 0.5f))
             .padding(12.dp)
             .selectableGroup(),
         horizontalAlignment = Alignment.Start,
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 e96c9d9..c4cea35 100644
--- a/tv/samples/src/main/java/androidx/tv/samples/NavigationDrawerSamples.kt
+++ b/tv/samples/src/main/java/androidx/tv/samples/NavigationDrawerSamples.kt
@@ -107,6 +107,8 @@
         "Favourites" to Icons.Default.Favorite,
     )
 
+    val closeDrawerWidth = 80.dp
+    val backgroundContentPadding = 10.dp
     ModalNavigationDrawer(
         drawerContent = {
             Column(
@@ -137,7 +139,13 @@
             }
         }
     ) {
-        Button(modifier = Modifier.height(100.dp).fillMaxWidth(), onClick = {}) {
+        Button(
+            modifier = Modifier
+                .padding(closeDrawerWidth + backgroundContentPadding)
+                .height(100.dp)
+                .fillMaxWidth(),
+            onClick = {}
+        ) {
             Text("BUTTON")
         }
     }
@@ -155,6 +163,9 @@
         "Favourites" to Icons.Default.Favorite,
     )
 
+    val closeDrawerWidth = 80.dp
+    val backgroundContentPadding = 10.dp
+
     ModalNavigationDrawer(
         drawerContent = {
             Column(
@@ -186,7 +197,13 @@
         },
         scrimBrush = Brush.horizontalGradient(listOf(Color.DarkGray, Color.Transparent))
     ) {
-        Button(modifier = Modifier.height(100.dp).fillMaxWidth(), onClick = {}) {
+        Button(
+            modifier = Modifier
+                .padding(closeDrawerWidth + backgroundContentPadding)
+                .height(100.dp)
+                .fillMaxWidth(),
+            onClick = {}
+        ) {
             Text("BUTTON")
         }
     }
diff --git a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/grid/LazyGridBeyondBoundsTest.kt b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/grid/LazyGridBeyondBoundsTest.kt
new file mode 100644
index 0000000..0913946
--- /dev/null
+++ b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/grid/LazyGridBeyondBoundsTest.kt
@@ -0,0 +1,634 @@
+/*
+ * 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.foundation.lazy.grid
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.layout.BeyondBoundsLayout
+import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Above
+import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.After
+import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Before
+import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Below
+import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Left
+import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Right
+import androidx.compose.ui.layout.ModifierLocalBeyondBoundsLayout
+import androidx.compose.ui.modifier.modifierLocalConsumer
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.LayoutDirection.Ltr
+import androidx.compose.ui.unit.LayoutDirection.Rtl
+import androidx.test.filters.MediumTest
+import androidx.tv.foundation.lazy.list.PlacementComparator
+import androidx.tv.foundation.lazy.list.TrackPlacedElement
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@OptIn(ExperimentalComposeUiApi::class)
+@MediumTest
+@RunWith(Parameterized::class)
+class LazyGridBeyondBoundsTest(param: Param) {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    // We need to wrap the inline class parameter in another class because Java can't instantiate
+    // the inline class.
+    class Param(
+        val beyondBoundsLayoutDirection: BeyondBoundsLayout.LayoutDirection,
+        val reverseLayout: Boolean,
+        val layoutDirection: LayoutDirection,
+    ) {
+        override fun toString() = "beyondBoundsLayoutDirection=$beyondBoundsLayoutDirection " +
+            "reverseLayout=$reverseLayout " +
+            "layoutDirection=$layoutDirection"
+    }
+
+    private val beyondBoundsLayoutDirection = param.beyondBoundsLayoutDirection
+    private val reverseLayout = param.reverseLayout
+    private val layoutDirection = param.layoutDirection
+    private val placedItems = sortedMapOf<Int, Rect>()
+    private var beyondBoundsLayout: BeyondBoundsLayout? = null
+    private lateinit var lazyGridState: TvLazyGridState
+    private val placementComparator =
+        PlacementComparator(beyondBoundsLayoutDirection, layoutDirection, reverseLayout)
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "{0}")
+        fun initParameters() = buildList {
+            for (beyondBoundsLayoutDirection in listOf(Left, Right, Above, Below, Before, After)) {
+                for (reverseLayout in listOf(false, true)) {
+                    for (layoutDirection in listOf(Ltr, Rtl)) {
+                        add(Param(beyondBoundsLayoutDirection, reverseLayout, layoutDirection))
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun onlyOneVisibleItemIsPlaced() {
+        // Arrange.
+        rule.setLazyContent(size = 10.toDp(), firstVisibleItem = 0) {
+            items(100) { index ->
+                Box(
+                    Modifier
+                        .size(10.toDp())
+                        .trackPlaced(index)
+                )
+            }
+        }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(placedItems.keys).containsExactly(0)
+            assertThat(visibleItems).containsExactly(0)
+        }
+    }
+
+    @Test
+    fun onlyTwoVisibleItemsArePlaced() {
+        // Arrange.
+        rule.setLazyContent(size = 20.toDp(), firstVisibleItem = 0) {
+            items(100) { index ->
+                Box(
+                    Modifier
+                        .size(10.toDp())
+                        .trackPlaced(index)
+                )
+            }
+        }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(placedItems.keys).containsExactly(0, 1)
+            assertThat(visibleItems).containsExactly(0, 1)
+        }
+    }
+
+    @Test
+    fun onlyThreeVisibleItemsArePlaced() {
+        // Arrange.
+        rule.setLazyContent(size = 30.toDp(), firstVisibleItem = 0) {
+            items(100) { index ->
+                Box(
+                    Modifier
+                        .size(10.toDp())
+                        .trackPlaced(index)
+                )
+            }
+        }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(placedItems.keys).containsExactly(0, 1, 2)
+            assertThat(visibleItems).containsExactly(0, 1, 2)
+        }
+    }
+
+    @Test
+    fun emptyLazyList_doesNotCrash() {
+        // Arrange.
+        var addItems by mutableStateOf(true)
+        lateinit var beyondBoundsLayoutRef: BeyondBoundsLayout
+        rule.setLazyContent(size = 30.toDp(), firstVisibleItem = 0) {
+            if (addItems) {
+                item {
+                    Box(
+                        Modifier.modifierLocalConsumer {
+                            beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
+                        }
+                    )
+                }
+            }
+        }
+        rule.runOnIdle {
+            beyondBoundsLayoutRef = beyondBoundsLayout!!
+            addItems = false
+        }
+
+        // Act.
+        val hasMoreContent = rule.runOnIdle {
+            beyondBoundsLayoutRef.layout(beyondBoundsLayoutDirection) {
+                hasMoreContent
+            }
+        }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(hasMoreContent).isFalse()
+        }
+    }
+
+    @Test
+    fun oneExtraItemBeyondVisibleBounds() {
+        // Arrange.
+        rule.setLazyContent(size = 30.toDp(), firstVisibleItem = 5) {
+            items(5) { index ->
+                Box(
+                    Modifier
+                        .size(10.toDp())
+                        .trackPlaced(index)
+                )
+            }
+            item {
+                Box(Modifier
+                    .size(10.toDp())
+                    .trackPlaced(5)
+                    .modifierLocalConsumer {
+                        beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
+                    }
+                )
+            }
+            items(5) { index ->
+                Box(
+                    Modifier
+                        .size(10.toDp())
+                        .trackPlaced(index + 6)
+                )
+            }
+        }
+
+        // Act.
+        rule.runOnUiThread {
+            beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
+                // Assert that the beyond bounds items are present.
+                if (expectedExtraItemsBeforeVisibleBounds()) {
+                    assertThat(placedItems.keys).containsExactly(4, 5, 6, 7)
+                } else {
+                    assertThat(placedItems.keys).containsExactly(5, 6, 7, 8)
+                }
+                assertThat(visibleItems).containsExactly(5, 6, 7)
+
+                assertThat(placedItems.values).isInOrder(placementComparator)
+
+                // Just return true so that we stop as soon as we run this once.
+                // This should result in one extra item being added.
+                true
+            }
+        }
+
+        // Assert that the beyond bounds items are removed.
+        rule.runOnIdle {
+            assertThat(placedItems.keys).containsExactly(5, 6, 7)
+            assertThat(visibleItems).containsExactly(5, 6, 7)
+        }
+    }
+
+    @Test
+    fun oneExtraItemBeyondVisibleBounds_multipleCells() {
+        val itemSize = 50
+        val itemSizeDp = itemSize.toDp()
+        // Arrange.
+        rule.setLazyContent(cells = 2, size = itemSizeDp * 3, firstVisibleItem = 10) {
+            // item | item  | x5
+            // item | local | x1
+            // item | item  | x5
+            items(11) { index ->
+                Box(
+                    Modifier
+                        .size(itemSizeDp)
+                        .trackPlaced(index)
+                )
+            }
+            item {
+                Box(Modifier
+                    .size(itemSizeDp)
+                    .trackPlaced(11)
+                    .modifierLocalConsumer {
+                        beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
+                    }
+                )
+            }
+            items(10) { index ->
+                Box(
+                    Modifier
+                        .size(itemSizeDp)
+                        .trackPlaced(index + 12)
+                )
+            }
+        }
+
+        // Act.
+        rule.runOnUiThread {
+            beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
+                // Assert that the beyond bounds items are present.
+                if (expectedExtraItemsBeforeVisibleBounds()) {
+                    assertThat(placedItems.keys).containsExactly(9, 10, 11, 12, 13, 14, 15)
+                } else {
+                    assertThat(placedItems.keys).containsExactly(10, 11, 12, 13, 14, 15, 16)
+                }
+                assertThat(visibleItems).containsExactly(10, 11, 12, 13, 14, 15)
+
+                assertThat(placedItems.values).isInOrder(placementComparator)
+
+                // Just return true so that we stop as soon as we run this once.
+                // This should result in one extra item being added.
+                true
+            }
+        }
+
+        // Assert that the beyond bounds items are removed.
+        rule.runOnIdle {
+            assertThat(placedItems.keys).containsExactly(10, 11, 12, 13, 14, 15)
+            assertThat(visibleItems).containsExactly(10, 11, 12, 13, 14, 15)
+        }
+    }
+
+    @Test
+    fun twoExtraItemsBeyondVisibleBounds() {
+        // Arrange.
+        var extraItemCount = 2
+        rule.setLazyContent(size = 30.toDp(), firstVisibleItem = 5) {
+            items(5) { index ->
+                Box(
+                    Modifier
+                        .size(10.toDp())
+                        .trackPlaced(index)
+                )
+            }
+            item {
+                Box(
+                    Modifier
+                        .size(10.toDp())
+                        .trackPlaced(5)
+                        .modifierLocalConsumer {
+                            beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
+                        }
+                )
+            }
+            items(5) { index ->
+                Box(
+                    Modifier
+                        .size(10.toDp())
+                        .trackPlaced(index + 6)
+                )
+            }
+        }
+
+        // Act.
+        rule.runOnUiThread {
+            beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
+                if (--extraItemCount > 0) {
+                    // Return null to continue the search.
+                    null
+                } else {
+                    // Assert that the beyond bounds items are present.
+                    if (expectedExtraItemsBeforeVisibleBounds()) {
+                        assertThat(placedItems.keys).containsExactly(3, 4, 5, 6, 7)
+                    } else {
+                        assertThat(placedItems.keys).containsExactly(5, 6, 7, 8, 9)
+                    }
+                    assertThat(visibleItems).containsExactly(5, 6, 7)
+
+                    assertThat(placedItems.values).isInOrder(placementComparator)
+
+                    // Return true to stop the search.
+                    true
+                }
+            }
+        }
+
+        // Assert that the beyond bounds items are removed.
+        rule.runOnIdle {
+            assertThat(placedItems.keys).containsExactly(5, 6, 7)
+            assertThat(visibleItems).containsExactly(5, 6, 7)
+        }
+    }
+
+    @Test
+    fun allBeyondBoundsItemsInSpecifiedDirection() {
+        // Arrange.
+        rule.setLazyContent(size = 30.toDp(), firstVisibleItem = 5) {
+            items(5) { index ->
+                Box(
+                    Modifier
+                        .size(10.toDp())
+                        .trackPlaced(index)
+                )
+            }
+            item {
+                Box(
+                    Modifier
+                        .size(10.toDp())
+                        .modifierLocalConsumer {
+                            beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
+                        }
+                        .trackPlaced(5)
+                )
+            }
+            items(5) { index ->
+                Box(
+                    Modifier
+                        .size(10.toDp())
+                        .trackPlaced(index + 6)
+                )
+            }
+        }
+
+        // Act.
+        rule.runOnUiThread {
+            beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
+                if (hasMoreContent) {
+                    // Just return null so that we keep adding more items till we reach the end.
+                    null
+                } else {
+                    // Assert that the beyond bounds items are present.
+                    if (expectedExtraItemsBeforeVisibleBounds()) {
+                        assertThat(placedItems.keys).containsExactly(0, 1, 2, 3, 4, 5, 6, 7)
+                    } else {
+                        assertThat(placedItems.keys).containsExactly(5, 6, 7, 8, 9, 10)
+                    }
+                    assertThat(visibleItems).containsExactly(5, 6, 7)
+
+                    assertThat(placedItems.values).isInOrder(placementComparator)
+
+                    // Return true to end the search.
+                    true
+                }
+            }
+        }
+
+        // Assert that the beyond bounds items are removed.
+        rule.runOnIdle {
+            assertThat(placedItems.keys).containsExactly(5, 6, 7)
+        }
+    }
+
+    @Test
+    fun beyondBoundsLayoutRequest_inDirectionPerpendicularToLazyListOrientation() {
+        // Arrange.
+        var beyondBoundsLayoutCount = 0
+        rule.setLazyContentInPerpendicularDirection(size = 30.toDp(), firstVisibleItem = 5) {
+            items(5) { index ->
+                Box(
+                    Modifier
+                        .size(10.toDp())
+                        .trackPlaced(index)
+                )
+            }
+            item {
+                Box(
+                    Modifier
+                        .size(10.toDp())
+                        .trackPlaced(5)
+                        .modifierLocalConsumer {
+                            beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
+                        }
+                )
+            }
+            items(5) { index ->
+                Box(
+                    Modifier
+                        .size(10.toDp())
+                        .trackPlaced(index + 6)
+                )
+            }
+        }
+        rule.runOnIdle {
+            assertThat(placedItems.keys).containsExactly(5, 6, 7)
+            assertThat(visibleItems).containsExactly(5, 6, 7)
+        }
+
+        // Act.
+        rule.runOnUiThread {
+            beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
+                beyondBoundsLayoutCount++
+                when (beyondBoundsLayoutDirection) {
+                    Left, Right, Above, Below -> {
+                        assertThat(placedItems.keys).containsExactly(5, 6, 7)
+                        assertThat(visibleItems).containsExactly(5, 6, 7)
+                    }
+                    Before, After -> {
+                        if (expectedExtraItemsBeforeVisibleBounds()) {
+                            assertThat(placedItems.keys).containsExactly(4, 5, 6, 7)
+                            assertThat(visibleItems).containsExactly(5, 6, 7)
+                        } else {
+                            assertThat(placedItems.keys).containsExactly(5, 6, 7, 8)
+                            assertThat(visibleItems).containsExactly(5, 6, 7)
+                        }
+                    }
+                }
+                // Just return true so that we stop as soon as we run this once.
+                // This should result in one extra item being added.
+                true
+            }
+        }
+
+        rule.runOnIdle {
+            when (beyondBoundsLayoutDirection) {
+                Left, Right, Above, Below -> {
+                    assertThat(beyondBoundsLayoutCount).isEqualTo(0)
+                }
+                Before, After -> {
+                    assertThat(beyondBoundsLayoutCount).isEqualTo(1)
+
+                    // Assert that the beyond bounds items are removed.
+                    assertThat(placedItems.keys).containsExactly(5, 6, 7)
+                    assertThat(visibleItems).containsExactly(5, 6, 7)
+                }
+                else -> error("Unsupported BeyondBoundsLayoutDirection")
+            }
+        }
+    }
+
+    @Test
+    fun returningNullDoesNotCauseInfiniteLoop() {
+        // Arrange.
+        rule.setLazyContent(size = 30.toDp(), firstVisibleItem = 5) {
+            items(5) { index ->
+                Box(
+                    Modifier
+                        .size(10.toDp())
+                        .trackPlaced(index)
+                )
+            }
+            item {
+                Box(
+                    Modifier
+                        .size(10.toDp())
+                        .modifierLocalConsumer {
+                            beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
+                        }
+                        .trackPlaced(5)
+                )
+            }
+            items(5) { index ->
+                Box(
+                    Modifier
+                        .size(10.toDp())
+                        .trackPlaced(index + 6)
+                )
+            }
+        }
+
+        // Act.
+        var count = 0
+        rule.runOnUiThread {
+            beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
+                // Assert that we don't keep iterating when there is no ending condition.
+                assertThat(count++).isLessThan(lazyGridState.layoutInfo.totalItemsCount)
+                // Always return null to continue the search.
+                null
+            }
+        }
+
+        // Assert that the beyond bounds items are removed.
+        rule.runOnIdle {
+            assertThat(placedItems.keys).containsExactly(5, 6, 7)
+            assertThat(visibleItems).containsExactly(5, 6, 7)
+        }
+    }
+
+    private fun ComposeContentTestRule.setLazyContent(
+        size: Dp,
+        firstVisibleItem: Int,
+        cells: Int = 1,
+        content: TvLazyGridScope.() -> Unit
+    ) {
+        setContent {
+            CompositionLocalProvider(LocalLayoutDirection provides layoutDirection) {
+                lazyGridState = rememberTvLazyGridState(firstVisibleItem)
+                when (beyondBoundsLayoutDirection) {
+                    Left, Right, Before, After ->
+                        TvLazyHorizontalGrid(
+                            rows = TvGridCells.Fixed(cells),
+                            modifier = Modifier.size(size),
+                            state = lazyGridState,
+                            reverseLayout = reverseLayout,
+                            content = content
+                        )
+                    Above, Below ->
+                        TvLazyVerticalGrid(
+                            columns = TvGridCells.Fixed(cells),
+                            modifier = Modifier.size(size),
+                            state = lazyGridState,
+                            reverseLayout = reverseLayout,
+                            content = content
+                        )
+                    else -> unsupportedDirection()
+                }
+            }
+        }
+    }
+
+    private fun ComposeContentTestRule.setLazyContentInPerpendicularDirection(
+        size: Dp,
+        firstVisibleItem: Int,
+        content: TvLazyGridScope.() -> Unit
+    ) {
+        setContent {
+            CompositionLocalProvider(LocalLayoutDirection provides layoutDirection) {
+                lazyGridState = rememberTvLazyGridState(firstVisibleItem)
+                when (beyondBoundsLayoutDirection) {
+                    Left, Right, Before, After ->
+                        TvLazyVerticalGrid(
+                            columns = TvGridCells.Fixed(1),
+                            modifier = Modifier.size(size),
+                            state = lazyGridState,
+                            reverseLayout = reverseLayout,
+                            content = content
+                        )
+                    Above, Below ->
+                        TvLazyHorizontalGrid(
+                            rows = TvGridCells.Fixed(1),
+                            modifier = Modifier.size(size),
+                            state = lazyGridState,
+                            reverseLayout = reverseLayout,
+                            content = content
+                        )
+                    else -> unsupportedDirection()
+                }
+            }
+        }
+    }
+
+    private fun Int.toDp(): Dp = with(rule.density) { toDp() }
+
+    private val visibleItems: List<Int>
+        get() = lazyGridState.layoutInfo.visibleItemsInfo.map { it.index }
+
+    private fun expectedExtraItemsBeforeVisibleBounds() = when (beyondBoundsLayoutDirection) {
+        Right -> if (layoutDirection == Ltr) reverseLayout else !reverseLayout
+        Left -> if (layoutDirection == Ltr) !reverseLayout else reverseLayout
+        Above -> !reverseLayout
+        Below -> reverseLayout
+        After -> false
+        Before -> true
+        else -> error("Unsupported BeyondBoundsDirection")
+    }
+
+    private fun unsupportedDirection(): Nothing = error(
+        "Lazy list does not support beyond bounds layout for the specified direction"
+    )
+
+    private fun Modifier.trackPlaced(index: Int): Modifier =
+        this then TrackPlacedElement(index, placedItems)
+}
diff --git a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListBeyondBoundsTest.kt b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListBeyondBoundsTest.kt
index 4544704..87f3c79 100644
--- a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListBeyondBoundsTest.kt
+++ b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListBeyondBoundsTest.kt
@@ -31,6 +31,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.input.key.NativeKeyEvent
 import androidx.compose.ui.layout.BeyondBoundsLayout
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Above
@@ -41,6 +42,7 @@
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Right
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.ModifierLocalBeyondBoundsLayout
+import androidx.compose.ui.layout.findRootCoordinates
 import androidx.compose.ui.modifier.modifierLocalConsumer
 import androidx.compose.ui.node.LayoutAwareModifierNode
 import androidx.compose.ui.node.ModifierNodeElement
@@ -84,9 +86,11 @@
     private val beyondBoundsLayoutDirection = param.beyondBoundsLayoutDirection
     private val reverseLayout = param.reverseLayout
     private val layoutDirection = param.layoutDirection
-    private val placedItems = mutableSetOf<Int>()
+    private val placedItems = sortedMapOf<Int, Rect>()
     private var beyondBoundsLayout: BeyondBoundsLayout? = null
     private lateinit var lazyListState: TvLazyListState
+    private val placementComparator =
+        PlacementComparator(beyondBoundsLayoutDirection, layoutDirection, reverseLayout)
 
     companion object {
         @JvmStatic
@@ -117,7 +121,7 @@
 
         // Assert.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(0)
+            assertThat(placedItems.keys).containsExactly(0)
             assertThat(visibleItems).containsExactly(0)
         }
     }
@@ -137,7 +141,7 @@
 
         // Assert.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(0, 1)
+            assertThat(placedItems.keys).containsExactly(0, 1)
             assertThat(visibleItems).containsExactly(0, 1)
         }
     }
@@ -157,7 +161,7 @@
 
         // Assert.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(0, 1, 2)
+            assertThat(placedItems.keys).containsExactly(0, 1, 2)
             assertThat(visibleItems).containsExactly(0, 1, 2)
         }
     }
@@ -210,11 +214,11 @@
             item {
                 Box(
                     Modifier
-                    .size(10.toDp())
-                    .trackPlaced(5)
-                    .modifierLocalConsumer {
-                        beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
-                    }
+                        .size(10.toDp())
+                        .trackPlaced(5)
+                        .modifierLocalConsumer {
+                            beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
+                        }
                 )
             }
             items(5) { index ->
@@ -231,12 +235,14 @@
             beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
                 // Assert that the beyond bounds items are present.
                 if (expectedExtraItemsBeforeVisibleBounds()) {
-                    assertThat(placedItems).containsExactly(4, 5, 6, 7)
-                    assertThat(visibleItems).containsExactly(5, 6, 7)
+                    assertThat(placedItems.keys).containsExactly(4, 5, 6, 7)
                 } else {
-                    assertThat(placedItems).containsExactly(5, 6, 7, 8)
-                    assertThat(visibleItems).containsExactly(5, 6, 7)
+                    assertThat(placedItems.keys).containsExactly(5, 6, 7, 8)
                 }
+                assertThat(visibleItems).containsExactly(5, 6, 7)
+
+                assertThat(placedItems.values).isInOrder(placementComparator)
+
                 // Just return true so that we stop as soon as we run this once.
                 // This should result in one extra item being added.
                 true
@@ -245,7 +251,7 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(placedItems.keys).containsExactly(5, 6, 7)
             assertThat(visibleItems).containsExactly(5, 6, 7)
         }
     }
@@ -290,12 +296,14 @@
                 } else {
                     // Assert that the beyond bounds items are present.
                     if (expectedExtraItemsBeforeVisibleBounds()) {
-                        assertThat(placedItems).containsExactly(3, 4, 5, 6, 7)
-                        assertThat(visibleItems).containsExactly(5, 6, 7)
+                        assertThat(placedItems.keys).containsExactly(3, 4, 5, 6, 7)
                     } else {
-                        assertThat(placedItems).containsExactly(5, 6, 7, 8, 9)
-                        assertThat(visibleItems).containsExactly(5, 6, 7)
+                        assertThat(placedItems.keys).containsExactly(5, 6, 7, 8, 9)
                     }
+                    assertThat(visibleItems).containsExactly(5, 6, 7)
+
+                    assertThat(placedItems.values).isInOrder(placementComparator)
+
                     // Return true to stop the search.
                     true
                 }
@@ -304,7 +312,7 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(placedItems.keys).containsExactly(5, 6, 7)
             assertThat(visibleItems).containsExactly(5, 6, 7)
         }
     }
@@ -348,12 +356,14 @@
                 } else {
                     // Assert that the beyond bounds items are present.
                     if (expectedExtraItemsBeforeVisibleBounds()) {
-                        assertThat(placedItems).containsExactly(0, 1, 2, 3, 4, 5, 6, 7)
-                        assertThat(visibleItems).containsExactly(5, 6, 7)
+                        assertThat(placedItems.keys).containsExactly(0, 1, 2, 3, 4, 5, 6, 7)
                     } else {
-                        assertThat(placedItems).containsExactly(5, 6, 7, 8, 9, 10)
-                        assertThat(visibleItems).containsExactly(5, 6, 7)
+                        assertThat(placedItems.keys).containsExactly(5, 6, 7, 8, 9, 10)
                     }
+                    assertThat(visibleItems).containsExactly(5, 6, 7)
+
+                    assertThat(placedItems.values).isInOrder(placementComparator)
+
                     // Return true to end the search.
                     true
                 }
@@ -362,7 +372,7 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(placedItems.keys).containsExactly(5, 6, 7)
         }
     }
 
@@ -397,7 +407,7 @@
             }
         }
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(placedItems.keys).containsExactly(5, 6, 7)
             assertThat(visibleItems).containsExactly(5, 6, 7)
         }
 
@@ -407,15 +417,15 @@
                 beyondBoundsLayoutCount++
                 when (beyondBoundsLayoutDirection) {
                     Left, Right, Above, Below -> {
-                        assertThat(placedItems).containsExactly(5, 6, 7)
+                        assertThat(placedItems.keys).containsExactly(5, 6, 7)
                         assertThat(visibleItems).containsExactly(5, 6, 7)
                     }
                     Before, After -> {
                         if (expectedExtraItemsBeforeVisibleBounds()) {
-                            assertThat(placedItems).containsExactly(4, 5, 6, 7)
+                            assertThat(placedItems.keys).containsExactly(4, 5, 6, 7)
                             assertThat(visibleItems).containsExactly(5, 6, 7)
                         } else {
-                            assertThat(placedItems).containsExactly(5, 6, 7, 8)
+                            assertThat(placedItems.keys).containsExactly(5, 6, 7, 8)
                             assertThat(visibleItems).containsExactly(5, 6, 7)
                         }
                     }
@@ -435,7 +445,7 @@
                     assertThat(beyondBoundsLayoutCount).isEqualTo(1)
 
                     // Assert that the beyond bounds items are removed.
-                    assertThat(placedItems).containsExactly(5, 6, 7)
+                    assertThat(placedItems.keys).containsExactly(5, 6, 7)
                     assertThat(visibleItems).containsExactly(5, 6, 7)
                 }
                 else -> error("Unsupported BeyondBoundsLayoutDirection")
@@ -486,7 +496,7 @@
 
         // Assert that the beyond bounds items are removed.
         rule.runOnIdle {
-            assertThat(placedItems).containsExactly(5, 6, 7)
+            assertThat(placedItems.keys).containsExactly(5, 6, 7)
             assertThat(visibleItems).containsExactly(5, 6, 7)
         }
     }
@@ -604,35 +614,60 @@
     )
 
     private fun Modifier.trackPlaced(index: Int): Modifier =
-        this then TrackPlacedElement(placedItems, index)
+        this then TrackPlacedElement(index, placedItems)
 }
 
 internal data class TrackPlacedElement(
-    var placedItems: MutableSet<Int>,
-    var index: Int
+    var index: Int,
+    var placedItems: MutableMap<Int, Rect>
 ) : ModifierNodeElement<TrackPlacedNode>() {
-    override fun create() = TrackPlacedNode(placedItems, index)
+    override fun create() = TrackPlacedNode(index, placedItems)
 
     override fun update(node: TrackPlacedNode) {
-        node.placedItems = placedItems
         node.index = index
+        node.placedItems = placedItems
     }
 
     override fun InspectorInfo.inspectableProperties() {
         name = "trackPlaced"
         properties["index"] = index
+        properties["placedItems"] = placedItems
     }
 }
 
 internal class TrackPlacedNode(
-    var placedItems: MutableSet<Int>,
-    var index: Int
+    var index: Int,
+    var placedItems: MutableMap<Int, Rect>
 ) : LayoutAwareModifierNode, Modifier.Node() {
     override fun onPlaced(coordinates: LayoutCoordinates) {
-        placedItems += index
+        placedItems[index] =
+            coordinates.findRootCoordinates().localBoundingBoxOf(coordinates, false)
     }
 
     override fun onDetach() {
-        placedItems -= index
+        placedItems.remove(index)
+    }
+}
+
+internal class PlacementComparator(
+    val beyondBoundsLayoutDirection: BeyondBoundsLayout.LayoutDirection,
+    val layoutDirection: LayoutDirection,
+    val reverseLayout: Boolean
+) : Comparator<Rect> {
+    private fun itemsInReverseOrder() = when (beyondBoundsLayoutDirection) {
+        Above, Below -> reverseLayout
+        else -> if (layoutDirection == Ltr) reverseLayout else !reverseLayout
+    }
+
+    private fun compareOffset(o1: Float, o2: Float): Int {
+        return if (itemsInReverseOrder()) o2.compareTo(o1) else o1.compareTo(o2)
+    }
+
+    override fun compare(o1: Rect?, o2: Rect?): Int {
+        if (o1 == null || o2 == null) return 0
+        return when (beyondBoundsLayoutDirection) {
+            Above, Below -> compareOffset(o1.top, o2.top)
+            else -> compareOffset(o1.left, o2.left)
+        }
     }
 }
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridMeasure.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridMeasure.kt
index f602de1..6431eaf 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridMeasure.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/grid/LazyGridMeasure.kt
@@ -28,6 +28,7 @@
 import androidx.compose.ui.unit.constrainHeight
 import androidx.compose.ui.unit.constrainWidth
 import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastForEachReversed
 import androidx.compose.ui.util.fastSumBy
 import androidx.tv.foundation.lazy.layout.LazyLayoutKeyIndexMap
 import androidx.tv.foundation.lazy.list.fastFilter
@@ -397,7 +398,7 @@
     } else {
         var currentMainAxis = firstLineScrollOffset
 
-        itemsBefore.fastForEach {
+        itemsBefore.fastForEachReversed {
             currentMainAxis -= it.mainAxisSizeWithSpacings
             it.position(currentMainAxis, 0, layoutWidth, layoutHeight)
             positionedItems.add(it)
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyListMeasure.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyListMeasure.kt
index e5396f5..5908cf4 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyListMeasure.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/lazy/list/LazyListMeasure.kt
@@ -29,6 +29,7 @@
 import androidx.compose.ui.util.fastAny
 import androidx.compose.ui.util.fastFirstOrNull
 import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastForEachReversed
 import kotlin.contracts.ExperimentalContracts
 import kotlin.contracts.contract
 import kotlin.math.abs
@@ -459,7 +460,7 @@
         list.add(measuredItemProvider.getAndMeasure(i))
     }
 
-    pinnedItems.fastForEach { index ->
+    pinnedItems.fastForEachReversed { index ->
         if (index < start) {
             if (list == null) list = mutableListOf()
             list?.add(measuredItemProvider.getAndMeasure(index))
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselTest.kt
index 5b302d8..c6569aa 100644
--- a/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselTest.kt
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselTest.kt
@@ -24,6 +24,7 @@
 import androidx.compose.animation.slideOutHorizontally
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
+import androidx.compose.foundation.focusGroup
 import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
@@ -44,14 +45,17 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusProperties
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.input.key.NativeKeyEvent
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsActions
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.assertIsFocused
 import androidx.compose.ui.test.assertIsNotFocused
@@ -63,6 +67,7 @@
 import androidx.compose.ui.test.onParent
 import androidx.compose.ui.test.onRoot
 import androidx.compose.ui.test.performKeyPress
+import androidx.compose.ui.test.performSemanticsAction
 import androidx.compose.ui.test.requestFocus
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
@@ -828,6 +833,80 @@
         // slide should have changed.
         rule.onNodeWithText("Left Button 2").assertIsFocused()
     }
+
+    @Test
+    fun carousel_manualScrollingLtr_loopsAroundWhenNoAdjacentFocusableItemsArePresent() {
+        rule.setContent {
+            // No AutoScrolling
+            SampleCarousel(timeToDisplayItemMillis = Long.MAX_VALUE, itemCount = 3) {
+                Row {
+                    SampleButton("Button-$it")
+                }
+            }
+        }
+
+        rule.onNodeWithText("Button-0")
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+
+        // Carousel should loop around the edges if there are no adjacent focusable items in the
+        // direction of dpad key press (left)
+        performKeyPress(KeyEvent.KEYCODE_DPAD_LEFT)
+        rule.onNodeWithText("Button-2").assertIsFocused()
+
+        // Carousel should loop around the edges if there are no adjacent focusable items in the
+        // direction of dpad key press (right)
+        performKeyPress(KeyEvent.KEYCODE_DPAD_RIGHT)
+        rule.onNodeWithText("Button-0").assertIsFocused()
+    }
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    @Test
+    fun carousel_manualScrollingLtr_focusMovesToAdjacentItemsOutsideCarousel() {
+        rule.setContent {
+            val focusRequester = remember { FocusRequester() }
+            Row {
+                Column(
+                    Modifier
+                        .focusProperties {
+                            enter = {
+                                focusRequester.requestFocus()
+                                FocusRequester.Cancel
+                            }
+                        }
+                        .focusGroup()
+                ) {
+                    repeat(3) {
+                        Box(
+                            modifier = Modifier
+                                .size(10.dp)
+                                .testTag("Item-$it")
+                                .then(
+                                    if (it == 0) Modifier.focusRequester(focusRequester)
+                                    else Modifier
+                                )
+                                .focusable()
+                        )
+                    }
+                }
+                // No AutoScrolling
+                Box(Modifier.weight(1f)) {
+                    SampleCarousel(timeToDisplayItemMillis = Long.MAX_VALUE, itemCount = 2) {
+                        Row {
+                            SampleButton("Button-$it")
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.onNodeWithText("Button-0")
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+
+        // Focus should exit Carousel if there are any adjacent focusable items in the direction
+        // of dpad key press (left)
+        performKeyPress(KeyEvent.KEYCODE_DPAD_LEFT)
+        rule.onNodeWithTag("Item-0").assertIsFocused()
+    }
 }
 
 @OptIn(ExperimentalTvMaterial3Api::class)
@@ -861,7 +940,6 @@
     )
 }
 
-@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalAnimationApi::class)
 @Composable
 private fun AnimatedContentScope.SampleCarouselItem(
     index: Int,
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/ModalNavigationDrawerTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/ModalNavigationDrawerTest.kt
index a522dc83..09a3c0f 100644
--- a/tv/tv-material/src/androidTest/java/androidx/tv/material3/ModalNavigationDrawerTest.kt
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/ModalNavigationDrawerTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.tv.material3
 
+import android.os.Build
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.focusable
@@ -23,6 +24,7 @@
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.text.BasicText
@@ -31,6 +33,8 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertContainsColor
+import androidx.compose.testutils.assertDoesNotContainColor
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusRequester
@@ -38,6 +42,7 @@
 import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.SolidColor
 import androidx.compose.ui.input.key.Key
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.testTag
@@ -49,7 +54,9 @@
 import androidx.compose.ui.test.assertIsEqualTo
 import androidx.compose.ui.test.assertIsFocused
 import androidx.compose.ui.test.assertIsNotFocused
+import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
 import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.getUnclippedBoundsInRoot
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onAllNodesWithText
@@ -62,6 +69,7 @@
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.toSize
+import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
 import org.junit.Rule
 import org.junit.Test
@@ -143,7 +151,7 @@
                             text = if (it == DrawerValue.Open) "Opened" else "Closed"
                         )
                     }) {
-                    Box(modifier = Modifier.focusable()) {
+                    Box(modifier = Modifier.padding(start = 100.dp).focusable()) {
                         BasicText("Button")
                     }
                 }
@@ -154,6 +162,7 @@
         }
         rule.onAllNodesWithText("Opened").assertAnyAreDisplayed()
         rule.onRoot().performKeyInput { pressKey(Key.DirectionRight) }
+
         rule.onAllNodesWithText("Closed").assertAnyAreDisplayed()
     }
 
@@ -182,6 +191,7 @@
                     }) {
                     Box(
                         modifier = Modifier
+                            .padding(start = 100.dp)
                             .focusRequester(buttonFocusRequester)
                             .focusable()
                     ) {
@@ -356,6 +366,79 @@
         rule.onNodeWithTag("box-container").assertIsFocused()
     }
 
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun modalNavigationDrawer_onOpen_scrimIsDrawnAboveContent() {
+        val backgroundContentColor = Color.Blue
+        val scrimColor = Color.Red
+        rule.setContent {
+            ModalNavigationDrawer(
+                drawerState = remember { DrawerState(DrawerValue.Open) },
+                drawerContent = {
+                    BasicText(text = if (it == DrawerValue.Open) "Opened" else "Closed")
+                },
+                scrimBrush = SolidColor(scrimColor)
+            ) { Box(Modifier.fillMaxSize().background(backgroundContentColor)) }
+        }
+
+        // the image should show only scrim color and no background content color
+        rule.onRoot().captureToImage().assertContainsColor(scrimColor)
+        rule.onRoot().captureToImage().assertDoesNotContainColor(backgroundContentColor)
+    }
+
+    @Test
+    fun modalNavigationDrawer_drawerOpen_backgroundContentShouldStartWithoutPadding() {
+        val backgroundContentColor = Color.Blue
+        rule.setContent {
+            ModalNavigationDrawer(
+                drawerState = remember { DrawerState(DrawerValue.Open) },
+                drawerContent = {
+                    BasicText(text = if (it == DrawerValue.Open) "Opened" else "Closed")
+                }
+            ) {
+                Box(
+                    modifier = Modifier
+                        .fillMaxSize()
+                        .background(backgroundContentColor)
+                        .testTag("background")
+                ) {
+                    Box(modifier = Modifier.padding(start = 100.dp).focusable()) {
+                        BasicText("Button")
+                    }
+                }
+            }
+        }
+
+        // the image should show only scrim color and no background content color
+        rule.onNodeWithTag("background").assertLeftPositionInRootIsEqualTo(0.dp)
+    }
+
+    fun modalNavigationDrawer_drawerClosed_backgroundContentShouldStartWithoutPadding() {
+        val backgroundContentColor = Color.Blue
+        rule.setContent {
+            ModalNavigationDrawer(
+                drawerState = remember { DrawerState(DrawerValue.Closed) },
+                drawerContent = {
+                    BasicText(text = if (it == DrawerValue.Open) "Opened" else "Closed")
+                }
+            ) {
+                Box(
+                    modifier = Modifier
+                        .fillMaxSize()
+                        .background(backgroundContentColor)
+                        .testTag("background")
+                ) {
+                    Box(modifier = Modifier.padding(start = 100.dp).focusable()) {
+                        BasicText("Button")
+                    }
+                }
+            }
+        }
+
+        // the image should show only scrim color and no background content color
+        rule.onNodeWithTag("background").assertLeftPositionInRootIsEqualTo(0.dp)
+    }
+
     private fun SemanticsNodeInteractionCollection.assertAnyAreDisplayed() {
         val result = (0 until fetchSemanticsNodes().size).map { get(it) }.any {
             try {
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/API_28_OR_ABOVE.kt b/tv/tv-material/src/main/java/androidx/tv/material3/API_28_OR_ABOVE.kt
new file mode 100644
index 0000000..7de9b2c
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/API_28_OR_ABOVE.kt
@@ -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.
+ */
+
+package androidx.tv.material3
+
+import android.os.Build
+
+internal val API_28_OR_ABOVE = Build.VERSION.SDK_INT >= 28
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt
index 23ffe4e..1f059dc 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt
@@ -291,7 +291,8 @@
                     KeyEventPropagation.StopPropagation
                 }
 
-            !focusManager.moveFocus(direction) -> {
+            !focusManager.moveFocus(direction) &&
+                currentCarouselBoxFocusState()?.hasFocus == true -> {
                 // if focus search was unsuccessful, interpret as input for slide change
                 updateItemBasedOnLayout(direction, isLtr)
                 KeyEventPropagation.StopPropagation
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/IfElseModifier.kt b/tv/tv-material/src/main/java/androidx/tv/material3/IfElseModifier.kt
new file mode 100644
index 0000000..44e8dac
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/IfElseModifier.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.ui.Modifier
+
+internal fun Modifier.ifElse(
+    condition: Boolean,
+    ifTrueModifier: Modifier,
+    ifFalseModifier: Modifier = Modifier
+): Modifier = then(if (condition) ifTrueModifier else ifFalseModifier)
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 320fe9e..3172056 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
@@ -23,7 +23,6 @@
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.MutableState
@@ -45,7 +44,6 @@
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.dp
 import androidx.compose.ui.zIndex
 
 /**
@@ -74,7 +72,9 @@
  * @param modifier the [Modifier] to be applied to this drawer
  * @param drawerState state of the drawer
  * @param scrimBrush brush to paint the scrim that obscures content when the drawer is open
- * @param content content of the rest of the UI
+ * @param content content of the rest of the UI. The content extends to the edge of the container
+ * under the modal navigation drawer. Focusable content that is not part of the background must have
+ * start-padding sufficient to prevent it from being drawn under the drawer in the Closed state.
  */
 @ExperimentalTvMaterial3Api
 @Composable
@@ -114,15 +114,14 @@
             content = drawerContent
         )
 
+        content()
+
         if (drawerState.currentValue == DrawerValue.Open) {
             // Scrim
             Canvas(Modifier.fillMaxSize()) {
                 drawRect(scrimBrush)
             }
         }
-        Box(Modifier.padding(start = closedDrawerWidth.value ?: ClosedDrawerWidth.dp)) {
-            content()
-        }
     }
 }
 
@@ -230,7 +229,6 @@
     }
 }
 
-@Suppress("IllegalExperimentalApiUsage") // TODO (b/233188423): Address before moving to beta
 @OptIn(ExperimentalTvMaterial3Api::class)
 @Composable
 private fun DrawerSheet(
@@ -275,5 +273,3 @@
         }
     }
 }
-
-private const val ClosedDrawerWidth = 80
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Surface.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Surface.kt
index 2546e1c..55da109 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/Surface.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Surface.kt
@@ -72,7 +72,8 @@
  * @param colors Defines the background & content color to be used in this Surface.
  * See [NonInteractiveSurfaceDefaults.colors].
  * @param border Defines a border around the Surface.
- * @param glow Diffused shadow to be shown behind the Surface.
+ * @param glow Diffused shadow to be shown behind the Surface. Note that glow is disabled for API
+ * levels below 28 as it is not supported by the underlying OS
  * @param content defines the [Composable] content inside the surface
  */
 @ExperimentalTvMaterial3Api
@@ -122,7 +123,8 @@
  * interaction states. See [ClickableSurfaceDefaults.colors].
  * @param scale Defines size of the Surface relative to its original size.
  * @param border Defines a border around the Surface.
- * @param glow Diffused shadow to be shown behind the Surface.
+ * @param glow Diffused shadow to be shown behind the Surface. Note that glow is disabled for API
+ * levels below 28 as it is not supported by the underlying OS
  * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
  * for this Surface. You can create and pass in your own remembered [MutableInteractionSource] if
  * you want to observe [Interaction]s and customize the appearance / behavior of this Surface in
@@ -226,7 +228,8 @@
  * interaction states. See [ToggleableSurfaceDefaults.colors].
  * @param scale Defines size of the Surface relative to its original size.
  * @param border Defines a border around the Surface.
- * @param glow Diffused shadow to be shown behind the Surface.
+ * @param glow Diffused shadow to be shown behind the Surface. Note that glow is disabled for API
+ * levels below 28 as it is not supported by the underlying OS
  * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
  * for this Surface. You can create and pass in your own remembered [MutableInteractionSource] if
  * you want to observe [Interaction]s and customize the appearance / behavior of this Surface in
@@ -357,23 +360,25 @@
             elevation = LocalAbsoluteTonalElevation.current
         )
 
+        val glowIndicationModifier = Modifier.indication(
+            interactionSource = interactionSource,
+            indication = rememberGlowIndication(
+                color = surfaceColorAtElevation(
+                    color = glow.elevationColor,
+                    elevation = glow.elevation
+                ),
+                shape = shape,
+                glowBlurRadius = glow.elevation
+            )
+        )
+
         Box(
             modifier = modifier
                 .indication(
                     interactionSource = interactionSource,
                     indication = remember(scale) { ScaleIndication(scale = scale) }
                 )
-                .indication(
-                    interactionSource = interactionSource,
-                    indication = rememberGlowIndication(
-                        color = surfaceColorAtElevation(
-                            color = glow.elevationColor,
-                            elevation = glow.elevation
-                        ),
-                        shape = shape,
-                        glowBlurRadius = glow.elevation
-                    )
-                )
+                .ifElse(API_28_OR_ABOVE, glowIndicationModifier)
                 // Increasing the zIndex of this Surface when it is in the focused state to
                 // avoid the glowIndication from being overlapped by subsequent items if
                 // this Surface is inside a list composable (like a Row/Column).
diff --git a/viewpager2/integration-tests/testapp/build.gradle b/viewpager2/integration-tests/testapp/build.gradle
index 28dd268..bc3fc1d 100644
--- a/viewpager2/integration-tests/testapp/build.gradle
+++ b/viewpager2/integration-tests/testapp/build.gradle
@@ -14,13 +14,6 @@
  * limitations under the License.
  */
 
-buildscript {
-    // TODO: Remove this when this test app no longer depends on 1.0.0 of vectordrawable-animated.
-    // vectordrawable and vectordrawable-animated were accidentally using the same package name
-    // which is no longer valid in namespaced resource world.
-    project.ext["android.uniquePackageNames"] = false
-}
-
 plugins {
     id("AndroidXPlugin")
     id("com.android.application")
diff --git a/wear/compose/integration-tests/demos/lint-baseline.xml b/wear/compose/integration-tests/demos/lint-baseline.xml
index b4978f9..6223fbbe 100644
--- a/wear/compose/integration-tests/demos/lint-baseline.xml
+++ b/wear/compose/integration-tests/demos/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.2.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.2.0-beta01)" variant="all" version="8.2.0-beta01">
+<issues format="6" by="lint 8.3.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
 
     <issue
         id="WearStandaloneAppFlag"
@@ -11,348 +11,6 @@
     </issue>
 
     <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="            Thread.sleep(300)"
-        errorLine2="                   ~~~~~">
-        <location
-            file="src/androidInstrumentedTest/kotlin/androidx/compose/material/BottomNavigationScreenshotTest.kt"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(300)"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidInstrumentedTest/kotlin/androidx/compose/material/ButtonScreenshotTest.kt"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(300)"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidInstrumentedTest/kotlin/androidx/compose/material/CheckboxScreenshotTest.kt"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(300)"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidInstrumentedTest/kotlin/androidx/compose/material/FloatingActionButtonScreenshotTest.kt"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(300)"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidInstrumentedTest/kotlin/androidx/compose/material/MaterialRippleThemeTest.kt"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="            Thread.sleep(300)"
-        errorLine2="                   ~~~~~">
-        <location
-            file="src/androidInstrumentedTest/kotlin/androidx/compose/material/MaterialRippleThemeTest.kt"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="            Thread.sleep(300)"
-        errorLine2="                   ~~~~~">
-        <location
-            file="src/androidInstrumentedTest/kotlin/androidx/compose/material/NavigationRailScreenshotTest.kt"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="    Thread.sleep(1)"
-        errorLine2="           ~~~~~">
-        <location
-            file="src/androidInstrumentedTest/kotlin/androidx/compose/material/ObservableThemeTest.kt"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(300)"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidInstrumentedTest/kotlin/androidx/compose/material/RadioButtonScreenshotTest.kt"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(300)"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidInstrumentedTest/kotlin/androidx/compose/material/RadioButtonScreenshotTest.kt"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(300)"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidInstrumentedTest/kotlin/androidx/compose/material/RadioButtonScreenshotTest.kt"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(300)"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidInstrumentedTest/kotlin/androidx/compose/material/SwitchScreenshotTest.kt"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(300)"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidInstrumentedTest/kotlin/androidx/compose/material/SwitchScreenshotTest.kt"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="        Thread.sleep(300)"
-        errorLine2="               ~~~~~">
-        <location
-            file="src/androidInstrumentedTest/kotlin/androidx/compose/material/SwitchScreenshotTest.kt"/>
-    </issue>
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="            Thread.sleep(300)"
-        errorLine2="                   ~~~~~">
-        <location
-            file="src/androidInstrumentedTest/kotlin/androidx/compose/material/TabScreenshotTest.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in constructor AnchoredDraggableState has parameter &apos;positionalThreshold&apos; with type Function1&lt;? super Float, Float>."
-        errorLine1="    internal val positionalThreshold: (totalDistance: Float) -> Float,"
-        errorLine2="                                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material/AnchoredDraggable.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in return type Function1&lt;Float, Float> of &apos;getPositionalThreshold$lint_module&apos;."
-        errorLine1="    internal val positionalThreshold: (totalDistance: Float) -> Float,"
-        errorLine2="                                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material/AnchoredDraggable.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in constructor AnchoredDraggableState has parameter &apos;velocityThreshold&apos; with type Function0&lt;Float>."
-        errorLine1="    internal val velocityThreshold: () -> Float,"
-        errorLine2="                                    ~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material/AnchoredDraggable.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in return type Function0&lt;Float> of &apos;getVelocityThreshold$lint_module&apos;."
-        errorLine1="    internal val velocityThreshold: () -> Float,"
-        errorLine2="                                    ~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material/AnchoredDraggable.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in constructor AnchoredDraggableState has parameter &apos;positionalThreshold&apos; with type Function1&lt;? super Float, Float>."
-        errorLine1="        positionalThreshold: (totalDistance: Float) -> Float,"
-        errorLine2="                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material/AnchoredDraggable.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in constructor AnchoredDraggableState has parameter &apos;velocityThreshold&apos; with type Function0&lt;Float>."
-        errorLine1="        velocityThreshold: () -> Float,"
-        errorLine2="                           ~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material/AnchoredDraggable.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method Saver has parameter &apos;positionalThreshold&apos; with type Function1&lt;? super Float, Float>."
-        errorLine1="            positionalThreshold: (distance: Float) -> Float,"
-        errorLine2="                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material/AnchoredDraggable.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method Saver has parameter &apos;velocityThreshold&apos; with type Function0&lt;Float>."
-        errorLine1="            velocityThreshold: () -> Float,"
-        errorLine2="                               ~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material/AnchoredDraggable.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in variable &apos;calculateBackLayerConstraints&apos; with type Function1&lt;? super Constraints, ? extends Constraints>."
-        errorLine1="    val calculateBackLayerConstraints: (Constraints) -> Constraints = {"
-        errorLine2="                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method BackdropStack has parameter &apos;calculateBackLayerConstraints&apos; with type Function1&lt;? super Constraints, Constraints>."
-        errorLine1="    calculateBackLayerConstraints: (Constraints) -> Constraints,"
-        errorLine2="                                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method BackdropStack has parameter &apos;frontLayer&apos; with type Function2&lt;? super Constraints, ? super Float, Unit>."
-        errorLine1="    frontLayer: @Composable @UiComposable (Constraints, Float) -> Unit"
-        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material/BackdropScaffold.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method BottomNavigationTransition has parameter &apos;content&apos; with type Function1&lt;? super Float, Unit>."
-        errorLine1="    content: @Composable (animationProgress: Float) -> Unit"
-        errorLine2="             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material/BottomNavigation.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method BottomSheet has parameter &apos;calculateAnchors&apos; with type Function1&lt;? super IntSize, ? extends DraggableAnchors&lt;BottomSheetValue>>."
-        errorLine1="    calculateAnchors: (sheetSize: IntSize) -> DraggableAnchors&lt;BottomSheetValue>,"
-        errorLine2="                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material/BottomSheetScaffold.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method BottomSheetScaffoldLayout has parameter &apos;bottomSheet&apos; with type Function1&lt;? super Integer, Unit>."
-        errorLine1="    bottomSheet: @Composable (layoutHeight: Int) -> Unit,"
-        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material/BottomSheetScaffold.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method BottomSheetScaffoldLayout has parameter &apos;sheetOffset&apos; with type Function0&lt;Float>."
-        errorLine1="    sheetOffset: () -> Float,"
-        errorLine2="                 ~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material/BottomSheetScaffold.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method awaitHorizontalPointerSlopOrCancellation has parameter &apos;onPointerSlopReached&apos; with type Function2&lt;? super PointerInputChange, ? super Float, Unit>."
-        errorLine1="    onPointerSlopReached: (change: PointerInputChange, overSlop: Float) -> Unit"
-        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material/DragGestureDetectorCopy.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method Scrim has parameter &apos;fraction&apos; with type Function0&lt;Float>."
-        errorLine1="    fraction: () -> Float,"
-        errorLine2="              ~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material/Drawer.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method updateHeight has parameter &apos;onHeightUpdate&apos; with type Function1&lt;? super Integer, Unit>."
-        errorLine1="    onHeightUpdate: (Int) -> Unit"
-        errorLine2="                    ~~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/material/ExposedDropdownMenu.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method NavigationRailTransition has parameter &apos;content&apos; with type Function1&lt;? super Float, Unit>."
-        errorLine1="    content: @Composable (animationProgress: Float) -> Unit"
-        errorLine2="             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material/NavigationRail.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method OutlinedTextFieldLayout has parameter &apos;onLabelMeasured&apos; with type Function1&lt;? super Size, Unit>."
-        errorLine1="    onLabelMeasured: (Size) -> Unit,"
-        errorLine2="                     ~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material/OutlinedTextField.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in constructor OutlinedTextFieldMeasurePolicy has parameter &apos;onLabelMeasured&apos; with type Function1&lt;? super Size, Unit>."
-        errorLine1="    private val onLabelMeasured: (Size) -> Unit,"
-        errorLine2="                                 ~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material/OutlinedTextField.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method intrinsicWidth has parameter &apos;intrinsicMeasurer&apos; with type Function2&lt;? super IntrinsicMeasurable, ? super Integer, Integer>."
-        errorLine1="        intrinsicMeasurer: (IntrinsicMeasurable, Int) -> Int"
-        errorLine2="                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material/OutlinedTextField.kt"/>
-    </issue>
-
-    <issue
-        id="PrimitiveInLambda"
-        message="Use a functional interface instead of lambda syntax for lambdas with primitive values in method intrinsicHeight has parameter &apos;intrinsicMeasurer&apos; with type Function2&lt;? super IntrinsicMeasurable, ? super Integer, Integer>."
-        errorLine1="        intrinsicMeasurer: (IntrinsicMeasurable, Int) -> Int"
-        errorLine2="                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/material/OutlinedTextField.kt"/>
-    </issue>
-
-    <issue
         id="PrimitiveInLambda"
         message="Use a functional interface instead of lambda syntax for lambdas with primitive values in variable &apos;pickerOption&apos; with type Function3&lt;? super PickerScope, ? super Integer, ? super Boolean, ? extends Unit>."
         errorLine1="        val pickerOption = pickerTextOption(textStyle) { &quot;%02d&quot;.format(it) }"
diff --git a/wear/tiles/tiles-material/lint-baseline.xml b/wear/tiles/tiles-material/lint-baseline.xml
index 9c080eb..046d30e 100644
--- a/wear/tiles/tiles-material/lint-baseline.xml
+++ b/wear/tiles/tiles-material/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.3.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
 
     <issue
         id="BanThreadSleep"
@@ -39,7 +39,7 @@
 
     <issue
         id="UnsafeOptInUsageError"
-        message="This declaration is opt-in and its usage should be marked with `@androidx.wear.tiles.TilesExperimental` or `@OptIn(markerClass = androidx.wear.tiles.TilesExperimental.class)`"
+        message="Failed to read `level` from `@androidx.wear.tiles.TilesExperimental` -- assuming `ERROR`. This declaration is opt-in and its usage should be marked with `@androidx.wear.tiles.TilesExperimental` or `@OptIn(markerClass = androidx.wear.tiles.TilesExperimental.class)`"
         errorLine1="                .setVariant(variant)"
         errorLine2="                 ~~~~~~~~~~">
         <location
@@ -48,7 +48,7 @@
 
     <issue
         id="UnsafeOptInUsageError"
-        message="This declaration is opt-in and its usage should be marked with `@androidx.wear.tiles.TilesExperimental` or `@OptIn(markerClass = androidx.wear.tiles.TilesExperimental.class)`"
+        message="Failed to read `level` from `@androidx.wear.tiles.TilesExperimental` -- assuming `ERROR`. This declaration is opt-in and its usage should be marked with `@androidx.wear.tiles.TilesExperimental` or `@OptIn(markerClass = androidx.wear.tiles.TilesExperimental.class)`"
         errorLine1="                androidx.wear.tiles.LayoutElementBuilders.FONT_WEIGHT_MEDIUM,"
         errorLine2="                                                          ~~~~~~~~~~~~~~~~~~">
         <location
@@ -57,7 +57,7 @@
 
     <issue
         id="UnsafeOptInUsageError"
-        message="This declaration is opt-in and its usage should be marked with `@androidx.wear.tiles.TilesExperimental` or `@OptIn(markerClass = androidx.wear.tiles.TilesExperimental.class)`"
+        message="Failed to read `level` from `@androidx.wear.tiles.TilesExperimental` -- assuming `ERROR`. This declaration is opt-in and its usage should be marked with `@androidx.wear.tiles.TilesExperimental` or `@OptIn(markerClass = androidx.wear.tiles.TilesExperimental.class)`"
         errorLine1="                androidx.wear.tiles.LayoutElementBuilders.FONT_WEIGHT_MEDIUM,"
         errorLine2="                                                          ~~~~~~~~~~~~~~~~~~">
         <location
@@ -66,7 +66,7 @@
 
     <issue
         id="UnsafeOptInUsageError"
-        message="This declaration is opt-in and its usage should be marked with `@androidx.wear.tiles.TilesExperimental` or `@OptIn(markerClass = androidx.wear.tiles.TilesExperimental.class)`"
+        message="Failed to read `level` from `@androidx.wear.tiles.TilesExperimental` -- assuming `ERROR`. This declaration is opt-in and its usage should be marked with `@androidx.wear.tiles.TilesExperimental` or `@OptIn(markerClass = androidx.wear.tiles.TilesExperimental.class)`"
         errorLine1="                androidx.wear.tiles.LayoutElementBuilders.FONT_WEIGHT_MEDIUM,"
         errorLine2="                                                          ~~~~~~~~~~~~~~~~~~">
         <location
@@ -75,7 +75,7 @@
 
     <issue
         id="UnsafeOptInUsageError"
-        message="This declaration is opt-in and its usage should be marked with `@androidx.wear.tiles.TilesExperimental` or `@OptIn(markerClass = androidx.wear.tiles.TilesExperimental.class)`"
+        message="Failed to read `level` from `@androidx.wear.tiles.TilesExperimental` -- assuming `ERROR`. This declaration is opt-in and its usage should be marked with `@androidx.wear.tiles.TilesExperimental` or `@OptIn(markerClass = androidx.wear.tiles.TilesExperimental.class)`"
         errorLine1="                androidx.wear.tiles.LayoutElementBuilders.FONT_WEIGHT_MEDIUM,"
         errorLine2="                                                          ~~~~~~~~~~~~~~~~~~">
         <location
@@ -84,7 +84,7 @@
 
     <issue
         id="UnsafeOptInUsageError"
-        message="This declaration is opt-in and its usage should be marked with `@androidx.wear.tiles.TilesExperimental` or `@OptIn(markerClass = androidx.wear.tiles.TilesExperimental.class)`"
+        message="Failed to read `level` from `@androidx.wear.tiles.TilesExperimental` -- assuming `ERROR`. This declaration is opt-in and its usage should be marked with `@androidx.wear.tiles.TilesExperimental` or `@OptIn(markerClass = androidx.wear.tiles.TilesExperimental.class)`"
         errorLine1="                androidx.wear.tiles.LayoutElementBuilders.FONT_WEIGHT_MEDIUM,"
         errorLine2="                                                          ~~~~~~~~~~~~~~~~~~">
         <location
@@ -93,7 +93,7 @@
 
     <issue
         id="UnsafeOptInUsageError"
-        message="This declaration is opt-in and its usage should be marked with `@androidx.wear.tiles.TilesExperimental` or `@OptIn(markerClass = androidx.wear.tiles.TilesExperimental.class)`"
+        message="Failed to read `level` from `@androidx.wear.tiles.TilesExperimental` -- assuming `ERROR`. This declaration is opt-in and its usage should be marked with `@androidx.wear.tiles.TilesExperimental` or `@OptIn(markerClass = androidx.wear.tiles.TilesExperimental.class)`"
         errorLine1="                androidx.wear.tiles.LayoutElementBuilders.FONT_WEIGHT_MEDIUM,"
         errorLine2="                                                          ~~~~~~~~~~~~~~~~~~">
         <location
@@ -102,7 +102,7 @@
 
     <issue
         id="UnsafeOptInUsageError"
-        message="This declaration is opt-in and its usage should be marked with `@androidx.wear.tiles.TilesExperimental` or `@OptIn(markerClass = androidx.wear.tiles.TilesExperimental.class)`"
+        message="Failed to read `level` from `@androidx.wear.tiles.TilesExperimental` -- assuming `ERROR`. This declaration is opt-in and its usage should be marked with `@androidx.wear.tiles.TilesExperimental` or `@OptIn(markerClass = androidx.wear.tiles.TilesExperimental.class)`"
         errorLine1="                androidx.wear.tiles.LayoutElementBuilders.FONT_WEIGHT_MEDIUM,"
         errorLine2="                                                          ~~~~~~~~~~~~~~~~~~">
         <location
@@ -111,7 +111,7 @@
 
     <issue
         id="UnsafeOptInUsageError"
-        message="This declaration is opt-in and its usage should be marked with `@androidx.wear.tiles.TilesExperimental` or `@OptIn(markerClass = androidx.wear.tiles.TilesExperimental.class)`"
+        message="Failed to read `level` from `@androidx.wear.tiles.TilesExperimental` -- assuming `ERROR`. This declaration is opt-in and its usage should be marked with `@androidx.wear.tiles.TilesExperimental` or `@OptIn(markerClass = androidx.wear.tiles.TilesExperimental.class)`"
         errorLine1="                androidx.wear.tiles.LayoutElementBuilders.FONT_WEIGHT_MEDIUM,"
         errorLine2="                                                          ~~~~~~~~~~~~~~~~~~">
         <location
@@ -120,7 +120,7 @@
 
     <issue
         id="UnsafeOptInUsageError"
-        message="This declaration is opt-in and its usage should be marked with `@androidx.wear.tiles.TilesExperimental` or `@OptIn(markerClass = androidx.wear.tiles.TilesExperimental.class)`"
+        message="Failed to read `level` from `@androidx.wear.tiles.TilesExperimental` -- assuming `ERROR`. This declaration is opt-in and its usage should be marked with `@androidx.wear.tiles.TilesExperimental` or `@OptIn(markerClass = androidx.wear.tiles.TilesExperimental.class)`"
         errorLine1="                androidx.wear.tiles.LayoutElementBuilders.FONT_WEIGHT_MEDIUM,"
         errorLine2="                                                          ~~~~~~~~~~~~~~~~~~">
         <location
diff --git a/wear/tiles/tiles-tooling-preview/api/current.txt b/wear/tiles/tiles-tooling-preview/api/current.txt
index e2c0e4e..f1331ab 100644
--- a/wear/tiles/tiles-tooling-preview/api/current.txt
+++ b/wear/tiles/tiles-tooling-preview/api/current.txt
@@ -19,11 +19,12 @@
   }
 
   public final class TilePreviewData {
-    ctor public TilePreviewData(optional kotlin.jvm.functions.Function2<? super androidx.wear.tiles.RequestBuilders.ResourcesRequest,? super android.content.Context,androidx.wear.protolayout.ResourceBuilders.Resources> onTileResourceRequest, kotlin.jvm.functions.Function2<? super androidx.wear.tiles.RequestBuilders.TileRequest,? super android.content.Context,androidx.wear.tiles.TileBuilders.Tile> onTileRequest);
-    method public kotlin.jvm.functions.Function2<androidx.wear.tiles.RequestBuilders.TileRequest,android.content.Context,androidx.wear.tiles.TileBuilders.Tile> getOnTileRequest();
-    method public kotlin.jvm.functions.Function2<androidx.wear.tiles.RequestBuilders.ResourcesRequest,android.content.Context,androidx.wear.protolayout.ResourceBuilders.Resources> getOnTileResourceRequest();
-    property public final kotlin.jvm.functions.Function2<androidx.wear.tiles.RequestBuilders.TileRequest,android.content.Context,androidx.wear.tiles.TileBuilders.Tile> onTileRequest;
-    property public final kotlin.jvm.functions.Function2<androidx.wear.tiles.RequestBuilders.ResourcesRequest,android.content.Context,androidx.wear.protolayout.ResourceBuilders.Resources> onTileResourceRequest;
+    ctor public TilePreviewData(optional kotlin.jvm.functions.Function1<? super androidx.wear.tiles.RequestBuilders.ResourcesRequest,androidx.wear.protolayout.ResourceBuilders.Resources> onTileResourceRequest, kotlin.jvm.functions.Function1<? super androidx.wear.tiles.RequestBuilders.TileRequest,androidx.wear.tiles.TileBuilders.Tile> onTileRequest);
+    ctor public TilePreviewData(kotlin.jvm.functions.Function1<? super androidx.wear.tiles.RequestBuilders.TileRequest,androidx.wear.tiles.TileBuilders.Tile> onTileRequest);
+    method public kotlin.jvm.functions.Function1<androidx.wear.tiles.RequestBuilders.TileRequest,androidx.wear.tiles.TileBuilders.Tile> getOnTileRequest();
+    method public kotlin.jvm.functions.Function1<androidx.wear.tiles.RequestBuilders.ResourcesRequest,androidx.wear.protolayout.ResourceBuilders.Resources> getOnTileResourceRequest();
+    property public final kotlin.jvm.functions.Function1<androidx.wear.tiles.RequestBuilders.TileRequest,androidx.wear.tiles.TileBuilders.Tile> onTileRequest;
+    property public final kotlin.jvm.functions.Function1<androidx.wear.tiles.RequestBuilders.ResourcesRequest,androidx.wear.protolayout.ResourceBuilders.Resources> onTileResourceRequest;
   }
 
   public final class TilePreviewHelper {
diff --git a/wear/tiles/tiles-tooling-preview/api/restricted_current.txt b/wear/tiles/tiles-tooling-preview/api/restricted_current.txt
index e2c0e4e..f1331ab 100644
--- a/wear/tiles/tiles-tooling-preview/api/restricted_current.txt
+++ b/wear/tiles/tiles-tooling-preview/api/restricted_current.txt
@@ -19,11 +19,12 @@
   }
 
   public final class TilePreviewData {
-    ctor public TilePreviewData(optional kotlin.jvm.functions.Function2<? super androidx.wear.tiles.RequestBuilders.ResourcesRequest,? super android.content.Context,androidx.wear.protolayout.ResourceBuilders.Resources> onTileResourceRequest, kotlin.jvm.functions.Function2<? super androidx.wear.tiles.RequestBuilders.TileRequest,? super android.content.Context,androidx.wear.tiles.TileBuilders.Tile> onTileRequest);
-    method public kotlin.jvm.functions.Function2<androidx.wear.tiles.RequestBuilders.TileRequest,android.content.Context,androidx.wear.tiles.TileBuilders.Tile> getOnTileRequest();
-    method public kotlin.jvm.functions.Function2<androidx.wear.tiles.RequestBuilders.ResourcesRequest,android.content.Context,androidx.wear.protolayout.ResourceBuilders.Resources> getOnTileResourceRequest();
-    property public final kotlin.jvm.functions.Function2<androidx.wear.tiles.RequestBuilders.TileRequest,android.content.Context,androidx.wear.tiles.TileBuilders.Tile> onTileRequest;
-    property public final kotlin.jvm.functions.Function2<androidx.wear.tiles.RequestBuilders.ResourcesRequest,android.content.Context,androidx.wear.protolayout.ResourceBuilders.Resources> onTileResourceRequest;
+    ctor public TilePreviewData(optional kotlin.jvm.functions.Function1<? super androidx.wear.tiles.RequestBuilders.ResourcesRequest,androidx.wear.protolayout.ResourceBuilders.Resources> onTileResourceRequest, kotlin.jvm.functions.Function1<? super androidx.wear.tiles.RequestBuilders.TileRequest,androidx.wear.tiles.TileBuilders.Tile> onTileRequest);
+    ctor public TilePreviewData(kotlin.jvm.functions.Function1<? super androidx.wear.tiles.RequestBuilders.TileRequest,androidx.wear.tiles.TileBuilders.Tile> onTileRequest);
+    method public kotlin.jvm.functions.Function1<androidx.wear.tiles.RequestBuilders.TileRequest,androidx.wear.tiles.TileBuilders.Tile> getOnTileRequest();
+    method public kotlin.jvm.functions.Function1<androidx.wear.tiles.RequestBuilders.ResourcesRequest,androidx.wear.protolayout.ResourceBuilders.Resources> getOnTileResourceRequest();
+    property public final kotlin.jvm.functions.Function1<androidx.wear.tiles.RequestBuilders.TileRequest,androidx.wear.tiles.TileBuilders.Tile> onTileRequest;
+    property public final kotlin.jvm.functions.Function1<androidx.wear.tiles.RequestBuilders.ResourcesRequest,androidx.wear.protolayout.ResourceBuilders.Resources> onTileResourceRequest;
   }
 
   public final class TilePreviewHelper {
diff --git a/wear/tiles/tiles-tooling-preview/src/main/java/androidx/wear/tiles/tooling/preview/TilePreview.kt b/wear/tiles/tiles-tooling-preview/src/main/java/androidx/wear/tiles/tooling/preview/TilePreview.kt
index 73aee32..84e1282 100644
--- a/wear/tiles/tiles-tooling-preview/src/main/java/androidx/wear/tiles/tooling/preview/TilePreview.kt
+++ b/wear/tiles/tiles-tooling-preview/src/main/java/androidx/wear/tiles/tooling/preview/TilePreview.kt
@@ -16,13 +16,42 @@
 
 package androidx.wear.tiles.tooling.preview
 
+import android.content.Context
 import androidx.annotation.FloatRange
 import androidx.wear.tooling.preview.devices.WearDevice
 import androidx.wear.tooling.preview.devices.WearDevices
 
 /**
- * The annotation that marks Tile preview components (functions that return [TilePreviewData]) that
- * should have a visual preview in the Android Studio preview panel.
+ * The annotation that marks Tile preview components that should have a visual preview in the
+ * Android Studio preview panel. Tile preview components are methods that take an optional [Context]
+ * parameter and return a [TilePreviewData]. Methods annotated with [TilePreview] must be top level
+ * declarations or in a top level class with a default constructor.
+ *
+ * For example:
+ * ```kotlin
+ * @TilePreview
+ * fun myTilePreview(): TilePreviewData {
+ *     return TilePreviewData { request -> myTile(request) }
+ * }
+ * ```
+ * or:
+ * ```kotlin
+ * @TilePreview
+ * fun myTilePreview(context: Context): TilePreviewData {
+ *     return TilePreviewData { request -> myTile(request, context) }
+ * }
+ * ```
+ *
+ * Because of the way previews are rendered within Android Studio, they are lightweight and don't
+ * require the whole Android framework to render them. However, this comes with the following
+ * limitations:
+ * * No network access
+ * * No file access
+ * * Some [Context] APIs may not be fully available, such as launching activities or retrieving
+ * services
+ *
+ * For more information, see
+ * https://developer.android.com/jetpack/compose/tooling/previews#preview-limitations
  *
  * The annotation contains a number of parameters that allow to define the way the Tile will be
  * rendered within the preview. The passed parameters are only read by Studio when rendering the
diff --git a/wear/tiles/tiles-tooling-preview/src/main/java/androidx/wear/tiles/tooling/preview/TilePreviewData.kt b/wear/tiles/tiles-tooling-preview/src/main/java/androidx/wear/tiles/tooling/preview/TilePreviewData.kt
index 10f2910..cd36965 100644
--- a/wear/tiles/tiles-tooling-preview/src/main/java/androidx/wear/tiles/tooling/preview/TilePreviewData.kt
+++ b/wear/tiles/tiles-tooling-preview/src/main/java/androidx/wear/tiles/tooling/preview/TilePreviewData.kt
@@ -16,7 +16,6 @@
 
 package androidx.wear.tiles.tooling.preview
 
-import android.content.Context
 import androidx.wear.protolayout.ResourceBuilders.Resources
 import androidx.wear.tiles.RequestBuilders.ResourcesRequest
 import androidx.wear.tiles.RequestBuilders.TileRequest
@@ -39,10 +38,10 @@
  *
  * @see [TilePreviewHelper.singleTimelineEntryTileBuilder]
  */
-class TilePreviewData(
-    val onTileResourceRequest: (ResourcesRequest, Context) -> Resources =
-        { _, _ -> defaultResources },
-    val onTileRequest: (TileRequest, Context) -> TileBuilders.Tile,
+class TilePreviewData
+@JvmOverloads constructor(
+    val onTileResourceRequest: (ResourcesRequest) -> Resources = { defaultResources },
+    val onTileRequest: (TileRequest) -> TileBuilders.Tile,
 ) {
     override fun toString(): String {
         return "TilePreviewData(onTileResourceRequest=$onTileResourceRequest," +
diff --git a/wear/tiles/tiles-tooling/src/androidTest/java/androidx/wear/tiles/tooling/TestTilePreviews.java b/wear/tiles/tiles-tooling/src/androidTest/java/androidx/wear/tiles/tooling/TestTilePreviews.java
new file mode 100644
index 0000000..7ffe0ac
--- /dev/null
+++ b/wear/tiles/tiles-tooling/src/androidTest/java/androidx/wear/tiles/tooling/TestTilePreviews.java
@@ -0,0 +1,115 @@
+/*
+ * 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.tiles.tooling;
+
+import static androidx.wear.protolayout.ColorBuilders.argb;
+import static androidx.wear.tiles.tooling.preview.TilePreviewHelper.singleTimelineEntryTileBuilder;
+
+import android.content.Context;
+
+import androidx.wear.protolayout.LayoutElementBuilders.FontStyle;
+import androidx.wear.protolayout.LayoutElementBuilders.Layout;
+import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement;
+import androidx.wear.protolayout.LayoutElementBuilders.Text;
+import androidx.wear.protolayout.ResourceBuilders.Resources;
+import androidx.wear.protolayout.TimelineBuilders.Timeline;
+import androidx.wear.protolayout.TimelineBuilders.TimelineEntry;
+import androidx.wear.tiles.TileBuilders.Tile;
+import androidx.wear.tiles.tooling.preview.TilePreview;
+import androidx.wear.tiles.tooling.preview.TilePreviewData;
+
+public class TestTilePreviews {
+    private static final String RESOURCES_VERSION = "1";
+    private static final Resources RESOURCES = new Resources.Builder().setVersion(
+            RESOURCES_VERSION).build();
+
+    private static LayoutElement layoutElement() {
+        return new Text.Builder()
+                .setText("Hello world!")
+                .setFontStyle(new FontStyle.Builder()
+                        .setColor(argb(0xFF000000))
+                        .build())
+                .build();
+    }
+
+    private static Layout layout() {
+        return new Layout.Builder()
+                .setRoot(layoutElement())
+                .build();
+    }
+
+    private static Tile tile() {
+        return new Tile.Builder()
+                .setResourcesVersion(RESOURCES_VERSION)
+                .setTileTimeline(new Timeline.Builder()
+                        .addTimelineEntry(new TimelineEntry.Builder()
+                                .setLayout(layout())
+                                .build())
+                        .build())
+                .build();
+    }
+
+    /** Declaration of a static tile preview method */
+    @TilePreview
+    public static TilePreviewData tilePreview() {
+        return new TilePreviewData((request) -> RESOURCES, (request) -> tile());
+    }
+
+    @TilePreview
+    static TilePreviewData tileLayoutPreview() {
+        return new TilePreviewData((request) -> singleTimelineEntryTileBuilder(layout()).build());
+    }
+
+    @TilePreview
+    static TilePreviewData tileLayoutElementPreview() {
+        return new TilePreviewData((request) ->
+                singleTimelineEntryTileBuilder(layoutElement()).build());
+    }
+
+    @TilePreview
+    private static TilePreviewData tilePreviewWithPrivateVisibility() {
+        return new TilePreviewData((request) -> tile());
+    }
+
+    static int duplicateFunctionName(int x) {
+        return x;
+    }
+
+    @TilePreview
+    static TilePreviewData duplicateFunctionName() {
+        return new TilePreviewData((request) -> tile());
+    }
+
+    @TilePreview
+    static TilePreviewData tilePreviewWithContextParameter(Context context) {
+        return new TilePreviewData((request) -> tile());
+    }
+
+    @TilePreview
+    static void tilePreviewWithWrongReturnType() {
+    }
+
+    @TilePreview
+    static TilePreviewData tilePreviewWithNonContextParameter(int i) {
+        return new TilePreviewData((request) -> tile());
+    }
+
+    @TilePreview
+    TilePreviewData nonStaticMethod() {
+        return new TilePreviewData((request) -> tile());
+    }
+}
diff --git a/wear/tiles/tiles-tooling/src/androidTest/java/androidx/wear/tiles/tooling/TestTilePreviews.kt b/wear/tiles/tiles-tooling/src/androidTest/java/androidx/wear/tiles/tooling/TestTilePreviews.kt
index 56bdd72..7ed3346 100644
--- a/wear/tiles/tiles-tooling/src/androidTest/java/androidx/wear/tiles/tooling/TestTilePreviews.kt
+++ b/wear/tiles/tiles-tooling/src/androidTest/java/androidx/wear/tiles/tooling/TestTilePreviews.kt
@@ -16,6 +16,7 @@
 
 package androidx.wear.tiles.tooling
 
+import android.content.Context
 import androidx.wear.protolayout.ColorBuilders.argb
 import androidx.wear.protolayout.LayoutElementBuilders
 import androidx.wear.protolayout.ResourceBuilders
@@ -51,25 +52,41 @@
     ).build()
 
 @TilePreview
-fun TilePreview() = TilePreviewData(
-    onTileResourceRequest = { _, _ -> resources },
-    onTileRequest = { _, _ -> tile() },
+fun tilePreview() = TilePreviewData(
+    onTileResourceRequest = { resources },
+    onTileRequest = { tile() },
 )
 
 @TilePreview
-fun TileLayoutPreview() = TilePreviewData { _, _ ->
+fun tileLayoutPreview() = TilePreviewData {
     singleTimelineEntryTileBuilder(layout()).build()
 }
 
 @TilePreview
-fun TileLayoutElementPreview() = TilePreviewData { _, _ ->
+fun tileLayoutElementPreview() = TilePreviewData {
     singleTimelineEntryTileBuilder(layoutElement()).build()
 }
 
 @TilePreview
-private fun TilePreviewWithPrivateVisibility() = TilePreviewData { _, _ -> tile() }
+private fun tilePreviewWithPrivateVisibility() = TilePreviewData { tile() }
 
 fun duplicateFunctionName(x: Int) = x
 
 @TilePreview
-fun duplicateFunctionName() = TilePreviewData { _, _ -> tile() }
+fun duplicateFunctionName() = TilePreviewData { tile() }
+
+@TilePreview
+fun tilePreviewWithContextParameter(@Suppress("UNUSED_PARAMETER") context: Context) =
+    TilePreviewData { tile() }
+
+@TilePreview
+fun tilePreviewWithWrongReturnType() = Unit
+
+@TilePreview
+fun tilePreviewWithNonContextParameter(@Suppress("UNUSED_PARAMETER") i: Int) =
+    TilePreviewData { tile() }
+
+class SomeClass {
+    @TilePreview
+    fun nonStaticMethod() = TilePreviewData { tile() }
+}
diff --git a/wear/tiles/tiles-tooling/src/androidTest/java/androidx/wear/tiles/tooling/TileServiceViewAdapterTest.kt b/wear/tiles/tiles-tooling/src/androidTest/java/androidx/wear/tiles/tooling/TileServiceViewAdapterTest.kt
index 32605b1..eeb74fc 100644
--- a/wear/tiles/tiles-tooling/src/androidTest/java/androidx/wear/tiles/tooling/TileServiceViewAdapterTest.kt
+++ b/wear/tiles/tiles-tooling/src/androidTest/java/androidx/wear/tiles/tooling/TileServiceViewAdapterTest.kt
@@ -27,10 +27,16 @@
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
 
-private const val TEST_TILE_PREVIEWS_FILE = "androidx.wear.tiles.tooling.TestTilePreviewsKt"
+private const val TEST_TILE_PREVIEWS_KOTLIN_FILE = "androidx.wear.tiles.tooling.TestTilePreviewsKt"
+private const val TEST_TILE_PREVIEWS_JAVA_FILE = "androidx.wear.tiles.tooling.TestTilePreviews"
 
-class TileServiceViewAdapterTest {
+@RunWith(Parameterized::class)
+class TileServiceViewAdapterTest(
+    private val testFile: String,
+) {
     @Suppress("DEPRECATION")
     @get:Rule
     val activityTestRule = androidx.test.rule.ActivityTestRule(TestActivity::class.java)
@@ -54,70 +60,99 @@
 
     @Test
     fun testTilePreview() {
-        initAndInflate("$TEST_TILE_PREVIEWS_FILE.TilePreview")
+        initAndInflate("$testFile.tilePreview")
 
-        activityTestRule.runOnUiThread {
-            val textView =
-                (tileServiceViewAdapter.getChildAt(0) as ViewGroup)
-                    .getChildAt(0) as TextView
-            assertNotNull(textView)
-            assertEquals("Hello world!", textView.text.toString())
-        }
+        assertThatTileHasInflatedSuccessfully()
     }
 
     @Test
     fun testTileLayoutPreview() {
-        initAndInflate("$TEST_TILE_PREVIEWS_FILE.TileLayoutPreview")
+        initAndInflate("$testFile.tileLayoutPreview")
 
-        activityTestRule.runOnUiThread {
-            val textView =
-                (tileServiceViewAdapter.getChildAt(0) as ViewGroup)
-                    .getChildAt(0) as TextView
-            assertNotNull(textView)
-            assertEquals("Hello world!", textView.text.toString())
-        }
+        assertThatTileHasInflatedSuccessfully()
     }
 
     @Test
     fun testTileLayoutElementPreview() {
-        initAndInflate("$TEST_TILE_PREVIEWS_FILE.TileLayoutElementPreview")
+        initAndInflate("$testFile.tileLayoutElementPreview")
 
-        activityTestRule.runOnUiThread {
-            val textView =
-                ((tileServiceViewAdapter.getChildAt(0) as ViewGroup)
-                    .getChildAt(0) as FrameLayout).getChildAt(0) as TextView
-            assertNotNull(textView)
-            assertEquals("Hello world!", textView.text.toString())
-        }
+        assertThatTileHasInflatedSuccessfully()
     }
 
     @Test
     fun testTilePreviewDeclaredWithPrivateMethod() {
-        initAndInflate("$TEST_TILE_PREVIEWS_FILE.TilePreviewWithPrivateVisibility")
+        initAndInflate("$testFile.tilePreviewWithPrivateVisibility")
 
-        activityTestRule.runOnUiThread {
-            val textView =
-                (tileServiceViewAdapter.getChildAt(0) as ViewGroup)
-                    .getChildAt(0) as TextView
-            assertNotNull(textView)
-            assertEquals("Hello world!", textView.text.toString())
-        }
+        assertThatTileHasInflatedSuccessfully()
     }
 
     @Test
     fun testTilePreviewThatHasSharedFunctionName() {
-        initAndInflate("$TEST_TILE_PREVIEWS_FILE.duplicateFunctionName")
+        initAndInflate("$testFile.duplicateFunctionName")
 
+        assertThatTileHasInflatedSuccessfully()
+    }
+
+    @Test
+    fun testTilePreviewWithContextParameter() {
+        initAndInflate("$testFile.tilePreviewWithContextParameter")
+
+        assertThatTileHasInflatedSuccessfully()
+    }
+
+    @Test
+    fun testTileWithWrongReturnTypeIsNotInflated() {
+        initAndInflate("$testFile.tilePreviewWithWrongReturnType")
+
+        assertThatTileHasNotInflated()
+    }
+
+    @Test
+    fun testTilePreviewWithNonContextParameterIsNotInflated() {
+        initAndInflate("$testFile.tilePreviewWithNonContextParameter")
+
+        assertThatTileHasNotInflated()
+    }
+
+    @Test
+    fun testNonStaticPreviewMethodWithDefaultConstructor() {
+        if (testFile == TEST_TILE_PREVIEWS_KOTLIN_FILE) {
+            initAndInflate("androidx.wear.tiles.tooling.SomeClass.nonStaticMethod")
+        } else {
+            initAndInflate("$testFile.nonStaticMethod")
+        }
+
+        assertThatTileHasInflatedSuccessfully()
+    }
+
+    private fun assertThatTileHasInflatedSuccessfully() {
         activityTestRule.runOnUiThread {
-            val textView =
-                (tileServiceViewAdapter.getChildAt(0) as ViewGroup)
-                    .getChildAt(0) as TextView
+            val textView = when (
+                val child = (tileServiceViewAdapter.getChildAt(0) as ViewGroup).getChildAt(0)
+            ) {
+                is TextView -> child
+                // layout elements are wrapped with a FrameLayout
+                else -> (child as? FrameLayout)?.getChildAt(0) as? TextView
+            }
             assertNotNull(textView)
-            assertEquals("Hello world!", textView.text.toString())
+            assertEquals("Hello world!", textView?.text.toString())
+        }
+    }
+
+    private fun assertThatTileHasNotInflated() {
+        activityTestRule.runOnUiThread {
+            assertEquals(0, tileServiceViewAdapter.childCount)
         }
     }
 
     companion object {
+        @Parameterized.Parameters
+        @JvmStatic
+        fun parameters() = listOf(
+            TEST_TILE_PREVIEWS_KOTLIN_FILE,
+            TEST_TILE_PREVIEWS_JAVA_FILE,
+        )
+
         class TestActivity : Activity() {
             override fun onCreate(savedInstanceState: Bundle?) {
                 super.onCreate(savedInstanceState)
diff --git a/wear/tiles/tiles-tooling/src/main/java/androidx/wear/tiles/tooling/TileServiceViewAdapter.kt b/wear/tiles/tiles-tooling/src/main/java/androidx/wear/tiles/tooling/TileServiceViewAdapter.kt
index e8a1a9c..d9e6909 100644
--- a/wear/tiles/tiles-tooling/src/main/java/androidx/wear/tiles/tooling/TileServiceViewAdapter.kt
+++ b/wear/tiles/tiles-tooling/src/main/java/androidx/wear/tiles/tooling/TileServiceViewAdapter.kt
@@ -32,6 +32,7 @@
 import androidx.wear.tiles.timeline.TilesTimelineCache
 import androidx.wear.tiles.tooling.preview.TilePreviewData
 import java.lang.reflect.Method
+import java.lang.reflect.Modifier
 import kotlin.math.roundToInt
 
 private const val TOOLS_NS_URI = "http://schemas.android.com/tools"
@@ -77,7 +78,7 @@
     }
 
     internal fun init(tilePreviewMethodFqn: String) {
-        val tilePreview = getTilePreview(tilePreviewMethodFqn)
+        val tilePreview = getTilePreview(tilePreviewMethodFqn) ?: return
         lateinit var tileRenderer: TileRenderer
         tileRenderer = TileRenderer(context, executor) { newState ->
             tileRenderer.previewTile(tilePreview, newState)
@@ -98,7 +99,7 @@
             .setDeviceConfiguration(deviceParams)
             .build()
 
-        val tile = tilePreview.onTileRequest(tileRequest, context).also { tile ->
+        val tile = tilePreview.onTileRequest(tileRequest).also { tile ->
             tile.state?.let { setState(it.keyToValueMapping) }
         }
         val layout = tile.tileTimeline?.getCurrentLayout() ?: return
@@ -107,7 +108,7 @@
             .setDeviceConfiguration(deviceParams)
             .setVersion(tile.resourcesVersion)
             .build()
-        val resources = tilePreview.onTileResourceRequest(resourcesRequest, context)
+        val resources = tilePreview.onTileResourceRequest(resourcesRequest)
 
         val inflateFuture = inflateAsync(layout, resources, this@TileServiceViewAdapter)
         inflateFuture.addListener({
@@ -116,20 +117,36 @@
             }
         }, executor)
     }
-}
 
-@SuppressLint("BanUncheckedReflection")
-internal fun getTilePreview(tilePreviewMethodFqn: String): TilePreviewData {
-    val className = tilePreviewMethodFqn.substringBeforeLast('.')
-    val methodName = tilePreviewMethodFqn.substringAfterLast('.')
+    @SuppressLint("BanUncheckedReflection")
+    internal fun getTilePreview(tilePreviewMethodFqn: String): TilePreviewData? {
+        val className = tilePreviewMethodFqn.substringBeforeLast('.')
+        val methodName = tilePreviewMethodFqn.substringAfterLast('.')
 
-    val method = Class.forName(className).declaredMethods.first {
-        it.name == methodName && it.parameterCount == 0
-    }.apply {
-        isAccessible = true
+        val methods = Class.forName(className).declaredMethods.filter { it.name == methodName }
+        methods.firstOrNull {
+            it.parameterCount == 1 && it.parameters.first().type == Context::class.java
+        }?.let { methodWithContextParameter ->
+            return invokeTilePreviewMethod(methodWithContextParameter, context)
+        }
+
+        return methods.firstOrNull {
+            it.name == methodName && it.parameterCount == 0
+        }?.let { methodWithoutContextParameter ->
+            return invokeTilePreviewMethod(methodWithoutContextParameter)
+        }
     }
 
-    return method.invoke(null) as TilePreviewData
+    @SuppressLint("BanUncheckedReflection")
+    private fun invokeTilePreviewMethod(method: Method, vararg args: Any?): TilePreviewData? {
+        method.isAccessible = true
+        return if (Modifier.isStatic(method.modifiers)) {
+            method.invoke(null, *args) as? TilePreviewData
+        } else {
+            val instance = method.declaringClass.getConstructor().newInstance()
+            method.invoke(instance, *args) as? TilePreviewData
+        }
+    }
 }
 
 internal fun TimelineBuilders.Timeline?.getCurrentLayout(): LayoutElementBuilders.Layout? {
diff --git a/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationTextTemplate.java b/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationTextTemplate.java
index d920085..0b19935 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationTextTemplate.java
+++ b/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationTextTemplate.java
@@ -41,12 +41,10 @@
 import java.io.ObjectOutputStream;
 import java.io.Serializable;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
-/**
- * Displays one or more ComplicationText objects in a template.
- *
- */
+/** Displays one or more ComplicationText objects in a template. */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @SuppressLint("BanParcelableUsage")
 public final class ComplicationTextTemplate implements Parcelable, TimeDependentText {
@@ -196,6 +194,17 @@
         return 0;
     }
 
+    @Override
+    @NonNull
+    public String toString() {
+        return "ComplicationTextTemplate{"
+                + "surroundingText="
+                + mSurroundingText
+                + ", complicationTexts="
+                + Arrays.toString(mComplicationTexts)
+                + "}";
+    }
+
     /**
      * Builder for a ComplicationTextTemplate object that displays one or more {@link
      * ComplicationText} objects, within a format string if specified.
diff --git a/wear/watchface/watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditorSessionTest.kt b/wear/watchface/watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditorSessionTest.kt
index 561aa31..5258a99 100644
--- a/wear/watchface/watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditorSessionTest.kt
+++ b/wear/watchface/watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditorSessionTest.kt
@@ -80,6 +80,7 @@
 import androidx.wear.watchface.complications.data.ComplicationType
 import androidx.wear.watchface.complications.data.EmptyComplicationData
 import androidx.wear.watchface.complications.data.LongTextComplicationData
+import androidx.wear.watchface.complications.data.NoDataComplicationData
 import androidx.wear.watchface.complications.data.PlainComplicationText
 import androidx.wear.watchface.complications.data.ShortTextComplicationData
 import androidx.wear.watchface.complications.rendering.CanvasComplicationDrawable
@@ -96,7 +97,6 @@
 import androidx.wear.watchface.style.WatchFaceLayer
 import androidx.wear.watchface.style.data.UserStyleWireFormat
 import com.google.common.truth.Truth.assertThat
-import java.lang.IllegalArgumentException
 import java.time.Instant
 import java.time.ZonedDateTime
 import java.util.concurrent.CountDownLatch
@@ -183,19 +183,19 @@
 private val placeholderWatchState = MutableWatchState().asWatchState()
 private val mockLeftCanvasComplication =
     CanvasComplicationDrawable(
-        ComplicationDrawable(),
+        ComplicationDrawable(ApplicationProvider.getApplicationContext()),
         placeholderWatchState,
         mockInvalidateCallback
     )
 private val mockRightCanvasComplication =
     CanvasComplicationDrawable(
-        ComplicationDrawable(),
+        ComplicationDrawable(ApplicationProvider.getApplicationContext()),
         placeholderWatchState,
         mockInvalidateCallback
     )
 private val mockBackgroundCanvasComplication =
     CanvasComplicationDrawable(
-        ComplicationDrawable(),
+        ComplicationDrawable(ApplicationProvider.getApplicationContext()),
         placeholderWatchState,
         mockInvalidateCallback
     )
@@ -901,7 +901,7 @@
     public fun fixedComplicationDataSource() {
         val mockLeftCanvasComplication =
             CanvasComplicationDrawable(
-                ComplicationDrawable(),
+                ComplicationDrawable(ApplicationProvider.getApplicationContext()),
                 placeholderWatchState,
                 mockInvalidateCallback
             )
@@ -1231,6 +1231,7 @@
                 .isEqualTo(Rect(120, 160, 160, 240))
             assertThat(editorSession.complicationsDataSourceInfo.value[LEFT_COMPLICATION_ID]!!.name)
                 .isEqualTo("DataSource1")
+            assertThat(leftComplication.complicationData.value).isEqualTo(NoDataComplicationData())
 
             /**
              * Invoke [TestComplicationHelperActivity] which will change the complication data
@@ -1247,6 +1248,10 @@
                 chosenComplicationDataSource.complicationDataSourceInfo
             )
 
+            // This should set the interactive complication to empty, preventing briefly seeing the
+            // old complication.
+            assertThat(leftComplication.complicationData.value).isEqualTo(EmptyComplicationData())
+
             // This should update the preview data to point to the updated DataSource3 data.
             val previewComplication =
                 editorSession.complicationsPreviewData.value[LEFT_COMPLICATION_ID]
@@ -1942,33 +1947,62 @@
     @Test
     @Suppress("Deprecation") // userStyleSettings
     public fun doNotCommit() {
+        ComplicationDataSourceChooserContract.useTestComplicationHelperActivity = true
+        TestComplicationHelperActivity.resultIntent =
+            CompletableDeferred(
+                Intent().apply {
+                    putExtra(
+                        ComplicationDataSourceChooserIntent.EXTRA_PROVIDER_INFO,
+                        ComplicationDataSourceInfo(
+                                "TestDataSource3App",
+                                "TestDataSource3",
+                                Icon.createWithBitmap(
+                                    Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+                                ),
+                                ComplicationType.LONG_TEXT,
+                                dataSource3
+                            )
+                            .toWireComplicationProviderInfo()
+                    )
+                }
+            )
         val scenario =
             createOnWatchFaceEditingTestActivity(
                 listOf(colorStyleSetting, watchHandStyleSetting),
-                emptyList(),
+                listOf(leftComplication, rightComplication),
                 previewScreenshotParams =
                     PreviewScreenshotParams(RenderParameters.DEFAULT_INTERACTIVE, Instant.EPOCH)
             )
+        lateinit var activity: OnWatchFaceEditingTestActivity
+        scenario.onActivity { activity = it }
+
         val editorObserver = TestEditorObserver()
         val observerId = EditorService.globalEditorService.registerObserver(editorObserver)
-        scenario.onActivity { activity ->
-            assertThat(editorDelegate.userStyle[colorStyleSetting]!!.id.value)
-                .isEqualTo(redStyleOption.id.value)
-            assertThat(editorDelegate.userStyle[watchHandStyleSetting]!!.id.value)
-                .isEqualTo(classicStyleOption.id.value)
 
-            // Select [blueStyleOption] and [gothicStyleOption].
-            val mutableUserStyle = activity.editorSession.userStyle.value.toMutableUserStyle()
-            for (userStyleSetting in activity.editorSession.userStyleSchema.userStyleSettings) {
-                mutableUserStyle[userStyleSetting] = userStyleSetting.options.last()
-            }
-            activity.editorSession.userStyle.value = mutableUserStyle.toUserStyle()
-
-            // This should cause the style on the to be reverted back to the initial style.
-            activity.editorSession.commitChangesOnClose = false
-            activity.editorSession.close()
-            activity.finish()
+        assertThat(editorDelegate.userStyle[colorStyleSetting]!!.id.value)
+            .isEqualTo(redStyleOption.id.value)
+        assertThat(editorDelegate.userStyle[watchHandStyleSetting]!!.id.value)
+            .isEqualTo(classicStyleOption.id.value)
+        // Select [blueStyleOption] and [gothicStyleOption].
+        val mutableUserStyle = activity.editorSession.userStyle.value.toMutableUserStyle()
+        for (userStyleSetting in activity.editorSession.userStyleSchema.userStyleSettings) {
+            mutableUserStyle[userStyleSetting] = userStyleSetting.options.last()
         }
+        activity.editorSession.userStyle.value = mutableUserStyle.toUserStyle()
+
+        assertThat(leftComplication.complicationData.value).isEqualTo(NoDataComplicationData())
+        // Select another complication.
+        runBlocking {
+            activity.editorSession.openComplicationDataSourceChooser(LEFT_COMPLICATION_ID)
+        }
+        // This should set the interactive complication to empty, preventing briefly seeing the
+        // old complication.
+        assertThat(leftComplication.complicationData.value).isEqualTo(EmptyComplicationData())
+
+        // This should cause the style on the to be reverted back to the initial style.
+        activity.editorSession.commitChangesOnClose = false
+        activity.editorSession.close()
+        activity.finish()
 
         val result =
             editorObserver
@@ -1981,12 +2015,13 @@
         assertFalse(result.shouldCommitChanges)
         assertNull(result.previewImage)
 
-        // The original style should be applied to the watch face however because
-        // commitChangesOnClose is false.
         assertThat(editorDelegate.userStyle[colorStyleSetting]!!.id.value)
             .isEqualTo(redStyleOption.id.value)
         assertThat(editorDelegate.userStyle[watchHandStyleSetting]!!.id.value)
             .isEqualTo(classicStyleOption.id.value)
+        // The original complication data and style should be applied to the watch face however
+        // because commitChangesOnClose is false.
+        assertThat(leftComplication.complicationData.value).isEqualTo(NoDataComplicationData())
 
         EditorService.globalEditorService.unregisterObserver(observerId)
     }
diff --git a/wear/watchface/watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt b/wear/watchface/watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
index acfcd72..8c92894 100644
--- a/wear/watchface/watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
+++ b/wear/watchface/watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
@@ -570,6 +570,8 @@
 
             try {
                 deferredComplicationPreviewDataAvailable.await()
+                val previousDataSourceInfo: ComplicationDataSourceInfo? =
+                    complicationsDataSourceInfo.value[complicationSlotId]
 
                 // Emit an updated complicationsDataSourceInfoMap.
                 complicationsDataSourceInfo.value =
@@ -589,6 +591,11 @@
                     HashMap(complicationsPreviewData.value).apply {
                         this[complicationSlotId] = previewData ?: EmptyComplicationData()
                     }
+                onComplicationUpdated(
+                    complicationSlotId,
+                    from = previousDataSourceInfo,
+                    to = complicationDataSourceChooserResult.dataSourceInfo,
+                )
 
                 return ChosenComplicationDataSource(
                     complicationSlotId,
@@ -779,6 +786,12 @@
     protected open val showComplicationDeniedDialogIntent: Intent? = null
 
     protected open val showComplicationRationaleDialogIntent: Intent? = null
+
+    protected open fun onComplicationUpdated(
+        complicationSlotId: Int,
+        from: ComplicationDataSourceInfo?,
+        to: ComplicationDataSourceInfo?,
+    ) {}
 }
 
 /**
@@ -936,6 +949,11 @@
         if (!commitChangesOnClose && this::previousWatchFaceUserStyle.isInitialized) {
             userStyle.value = previousWatchFaceUserStyle
         }
+        if (this::editorDelegate.isInitialized) {
+            editorDelegate.complicationSlotsManager.unfreezeAllSlotsForEdit(
+                clearData = commitChangesOnClose
+            )
+        }
 
         if (this::fetchComplicationsDataJob.isInitialized) {
             // Wait until the fetchComplicationsDataJob has finished and released the
@@ -995,6 +1013,18 @@
         requireNotClosed()
         return editorDelegate.complicationSlotsManager.getComplicationSlotAt(x, y)?.id
     }
+
+    override fun onComplicationUpdated(
+        complicationSlotId: Int,
+        from: ComplicationDataSourceInfo?,
+        to: ComplicationDataSourceInfo?,
+    ) {
+        editorDelegate.complicationSlotsManager.freezeSlotForEdit(
+            complicationSlotId,
+            from = from,
+            to = to,
+        )
+    }
 }
 
 @RequiresApi(27)
diff --git a/wear/watchface/watchface-style/src/main/java/androidx/wear/watchface/style/CurrentUserStyleRepository.kt b/wear/watchface/watchface-style/src/main/java/androidx/wear/watchface/style/CurrentUserStyleRepository.kt
index 33b865a..4a563e0 100644
--- a/wear/watchface/watchface-style/src/main/java/androidx/wear/watchface/style/CurrentUserStyleRepository.kt
+++ b/wear/watchface/watchface-style/src/main/java/androidx/wear/watchface/style/CurrentUserStyleRepository.kt
@@ -722,6 +722,15 @@
         mutableUserStyle.value = newUserStyle
     }
 
+    /** Sets the user style, and returns a restoration function. */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public fun updateUserStyleForScreenshot(newUserStyle: UserStyle): AutoCloseable {
+        val originalStyle = userStyle.value
+        updateUserStyle(newUserStyle)
+        // Avoid overwriting a change made by someone else.
+        return AutoCloseable { mutableUserStyle.compareAndSet(newUserStyle, originalStyle) }
+    }
+
     @Suppress("Deprecation") // userStyleSettings
     internal fun validateUserStyle(userStyle: UserStyle) {
         for ((key, value) in userStyle) {
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 e8f57d7..b89aaba 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
@@ -31,6 +31,7 @@
 import androidx.annotation.VisibleForTesting
 import androidx.annotation.WorkerThread
 import androidx.wear.watchface.RenderParameters.HighlightedElement
+import androidx.wear.watchface.complications.ComplicationDataSourceInfo
 import androidx.wear.watchface.complications.ComplicationSlotBounds
 import androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy
 import androidx.wear.watchface.complications.data.ComplicationData
@@ -1004,11 +1005,42 @@
     internal var dataDirty = true
 
     /**
+     * The data set by [setComplicationData] (and then selected by
+     * [selectComplicationDataForInstant]). Exposed by [complicationData] unless
+     * [frozenDataSourceForEdit] is set.
+     */
+    private var selectedData: ComplicationData = NoDataComplicationData()
+
+    private data class FrozenDataSourceForEdit(
+        val from: ComplicationDataSourceInfo?,
+        val to: ComplicationDataSourceInfo?,
+    )
+
+    /**
+     * Marks the slot frozen, so [complicationData] only returns [EmptyComplicationData].
+     *
+     * This reduces the chances of the slot showing the previous complication momentarily when the
+     * user finishes editing.
+     *
+     * Memorizing from/to edited data source because we need to avoid clearing the complication data
+     * when the data source is the same, because the platform doesn't re-fetch complications when
+     * only updating configuration.
+     */
+    private var frozenDataSourceForEdit: FrozenDataSourceForEdit? = null
+
+    /**
      * The [androidx.wear.watchface.complications.data.ComplicationData] associated with the
      * [ComplicationSlot]. This defaults to [NoDataComplicationData].
+     *
+     * If the slot is frozen for edit, this is set to [EmptyComplicationData].
      */
-    public val complicationData: StateFlow<ComplicationData> =
-        MutableStateFlow(NoDataComplicationData())
+    // Can be described as:
+    //   selectedData.combine(frozenDataSourceForEdit) { data, frozenDataSource ->
+    //     if (frozenDataSource == null) data else EmptyComplicationData()
+    //   }
+    // but some flows depend on this StateFlow updating immediately after selectedData was changed,
+    // and Flow.combine() doesn't ensure that.
+    public val complicationData: StateFlow<ComplicationData> = MutableStateFlow(selectedData)
 
     /**
      * The complication data sent by the system. This may contain a timeline out of which
@@ -1023,34 +1055,55 @@
      * and the complication history is updated.
      */
     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,
-            forceUpdate = true
-        )
+        setTimelineData(complicationData, instant)
+        selectComplicationDataForInstant(instant, 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.
+     *
+     * Returns a restoration function.
      */
     internal fun setComplicationDataForScreenshot(
         complicationData: ComplicationData,
         instant: Instant
-    ) {
+    ): AutoCloseable {
+        val originalComplicationData = timelineComplicationData
+        val originalInstant = lastComplicationUpdate
+        val restore = AutoCloseable {
+            // Avoid overwriting a change made by someone else, can still race.
+            if (timelineComplicationData !== complicationData) return@AutoCloseable
+            setTimelineData(originalComplicationData, originalInstant)
+            selectComplicationDataForInstant(
+                originalInstant,
+                forceUpdate = true,
+                forScreenshot = false,
+            )
+        }
+
+        try {
+            setTimelineData(complicationData, instant)
+            selectComplicationDataForInstant(instant, forceUpdate = true, forScreenshot = true)
+        } catch (e: Throwable) {
+            // Cleanup on failure.
+            restore.close()
+            throw e
+        }
+        return restore
+    }
+
+    private fun setTimelineData(data: ComplicationData, instant: Instant) {
         lastComplicationUpdate = instant
-        timelineComplicationData = complicationData
-        timelineEntries = complicationData.asWireComplicationData().timelineEntries?.toList()
-        selectComplicationDataForInstant(
-            instant,
-            loadDrawablesAsynchronous = false,
-            forceUpdate = true
-        )
+        timelineComplicationData = data
+        timelineEntries = data.asWireComplicationData().timelineEntries?.toList()
+    }
+
+    private fun loadData(data: ComplicationData, loadDrawablesAsynchronous: Boolean = false) {
+        renderer.loadData(data, loadDrawablesAsynchronous = loadDrawablesAsynchronous)
+        (complicationData as MutableStateFlow<ComplicationData>).value = data
     }
 
     /**
@@ -1059,8 +1112,8 @@
      */
     internal fun selectComplicationDataForInstant(
         instant: Instant,
-        loadDrawablesAsynchronous: Boolean,
-        forceUpdate: Boolean
+        forceUpdate: Boolean,
+        forScreenshot: Boolean = false
     ) {
         var previousShortest = Long.MAX_VALUE
         val time = instant.epochSecond
@@ -1090,14 +1143,48 @@
             best = screenLockedFallback // This is NoDataComplicationData.
         }
 
-        if (!forceUpdate && complicationData.value == best) return
-        renderer.loadData(best, loadDrawablesAsynchronous)
-        (complicationData as MutableStateFlow).value = best
+        if (!forceUpdate && selectedData == best) return
+
+        val frozen = frozenDataSourceForEdit != null
+        if (!frozen || forScreenshot) {
+            loadData(best, loadDrawablesAsynchronous = !forScreenshot)
+        } else {
+            // Restoring frozen slot to empty in case it was changed for screenshot.
+            loadData(EmptyComplicationData())
+        }
+        selectedData = best
 
         // forceUpdate is used for screenshots, don't set the dirty flag for those.
-        if (!forceUpdate) {
-            dataDirty = true
+        if (!forceUpdate) dataDirty = true
+    }
+
+    /** Sets [frozenDataSourceForEdit]. */
+    internal fun freezeForEdit(
+        from: ComplicationDataSourceInfo?,
+        to: ComplicationDataSourceInfo?,
+    ) {
+        val previous = frozenDataSourceForEdit
+        // Keeping the original "from" of the first edit.
+        frozenDataSourceForEdit = FrozenDataSourceForEdit(from = previous?.from ?: from, to = to)
+        // If this is the first freeze, render EmptyComplicationData.
+        if (previous == null) loadData(EmptyComplicationData())
+    }
+
+    /** Unsets [frozenDataSourceForEdit]. */
+    internal fun unfreezeForEdit(clearData: Boolean) {
+        val frozenDataSourceForEdit = frozenDataSourceForEdit ?: return
+        // Clearing the previously selected data if needed.
+        if (
+            clearData &&
+                frozenDataSourceForEdit.from?.componentName !=
+                    frozenDataSourceForEdit.to?.componentName
+        ) {
+            setComplicationData(EmptyComplicationData(), Instant.now())
         }
+        this.frozenDataSourceForEdit = null
+        // Re-load current/new data immediately
+        // (especially in case of new data skipped loading in selectComplicationDataForInstant).
+        loadData(selectedData)
     }
 
     /**
@@ -1189,7 +1276,8 @@
 
         if (isHeadless) {
             timelineComplicationData = EmptyComplicationData()
-            (complicationData as MutableStateFlow).value = EmptyComplicationData()
+            selectedData = EmptyComplicationData()
+            (complicationData as MutableStateFlow<ComplicationData>).value = EmptyComplicationData()
         }
     }
 
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 22d34a0..d90b9d3 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
@@ -28,6 +28,7 @@
 import androidx.annotation.UiThread
 import androidx.annotation.VisibleForTesting
 import androidx.annotation.WorkerThread
+import androidx.wear.watchface.complications.ComplicationDataSourceInfo
 import androidx.wear.watchface.complications.ComplicationSlotBounds
 import androidx.wear.watchface.complications.data.ComplicationData
 import androidx.wear.watchface.complications.data.ComplicationExperimental
@@ -331,24 +332,36 @@
     }
 
     /**
-     * For use by screen shot code which will reset the data afterwards, hence dirty bit not set.
+     * Sets complication data, returning a restoration function.
+     *
+     * As this is used for screen shots, dirty bit (used for content description) is not set.
      */
     @UiThread
-    internal fun setComplicationDataUpdateForScreenshot(
-        complicationSlotId: Int,
-        data: ComplicationData,
-        instant: Instant
-    ) {
-        val complication = complicationSlots[complicationSlotId]
-        if (complication == null) {
-            Log.e(
-                TAG,
-                "setComplicationDataUpdateSync failed due to invalid complicationSlotId=" +
-                    "$complicationSlotId with data=$data"
-            )
-            return
+    internal fun setComplicationDataForScreenshot(
+        slotIdToData: Map<Int, ComplicationData>,
+        instant: Instant,
+    ): AutoCloseable {
+        val restores = mutableListOf<AutoCloseable>()
+        val restore = AutoCloseable { restores.forEach(AutoCloseable::close) }
+        try {
+            for ((id, data) in slotIdToData) {
+                val slot = complicationSlots[id]
+                if (slot == null) {
+                    Log.e(
+                        TAG,
+                        "setComplicationDataForScreenshot failed due to invalid " +
+                            "complicationSlotId=$id with data=$data"
+                    )
+                    continue
+                }
+                restores.add(slot.setComplicationDataForScreenshot(data, instant))
+            }
+        } catch (e: Throwable) {
+            // Cleanup changes on failure.
+            restore.close()
+            throw e
         }
-        complication.setComplicationDataForScreenshot(data, instant)
+        return restore
     }
 
     /**
@@ -358,11 +371,7 @@
     @UiThread
     internal fun selectComplicationDataForInstant(instant: Instant) {
         for ((_, complication) in complicationSlots) {
-            complication.selectComplicationDataForInstant(
-                instant,
-                loadDrawablesAsynchronous = true,
-                forceUpdate = false
-            )
+            complication.selectComplicationDataForInstant(instant, forceUpdate = false)
         }
 
         // selectComplicationDataForInstant may have changed the complication, if so we need to
@@ -372,6 +381,22 @@
         }
     }
 
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public fun freezeSlotForEdit(
+        slotId: Int,
+        from: ComplicationDataSourceInfo?,
+        to: ComplicationDataSourceInfo?,
+    ) {
+        complicationSlots[slotId]?.freezeForEdit(from = from, to = to)
+    }
+
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public fun unfreezeAllSlotsForEdit(clearData: Boolean) {
+        for (slot in complicationSlots.values) {
+            slot.unfreezeForEdit(clearData)
+        }
+    }
+
     /**
      * Returns the id of the complication slot at coordinates x, y or `null` if there isn't one.
      * Initially checks slots without margins (should be no overlaps) then then if there was no hit
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 74af498..270373f 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
@@ -665,27 +665,30 @@
     internal var nextDrawTimeMillis: Long = 0
 
     internal val componentName = watchFaceHostApi.getComponentName()
+    private val displayManager: DisplayManager
+    private val displayListener: DisplayManager.DisplayListener
 
     init {
         val context = watchFaceHostApi.getContext()
-        val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
-        displayManager.registerDisplayListener(
-            object : DisplayManager.DisplayListener {
-                override fun onDisplayAdded(displayId: Int) {}
+         displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
+         displayListener = 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) {}
-            },
+            override fun onDisplayRemoved(displayId: Int) {}
+        }
+        displayManager.registerDisplayListener(
+            displayListener,
             watchFaceHostApi.getUiThreadHandler()
         )
     }
@@ -905,37 +908,14 @@
             slotIdToComplicationData: Map<Int, ComplicationData>?
         ): Bitmap =
             TraceEvent("WFEditorDelegate.takeScreenshot").use {
-                val oldComplicationData =
-                    complicationSlotsManager.complicationSlots.values.associateBy(
-                        { it.id },
-                        { it.renderer.getData() }
-                    )
-
-                slotIdToComplicationData?.let {
-                    for ((id, complicationData) in it) {
-                        complicationSlotsManager.setComplicationDataUpdateForScreenshot(
-                            id,
-                            complicationData,
-                            instant
+                slotIdToComplicationData
+                    ?.let { complicationSlotsManager.setComplicationDataForScreenshot(it, instant) }
+                    .use {
+                        renderer.takeScreenshot(
+                            ZonedDateTime.ofInstant(instant, ZoneId.of("UTC")),
+                            renderParameters
                         )
                     }
-                }
-                val screenShot =
-                    renderer.takeScreenshot(
-                        ZonedDateTime.ofInstant(instant, ZoneId.of("UTC")),
-                        renderParameters
-                    )
-                slotIdToComplicationData?.let {
-                    val now = getNow()
-                    for ((id, complicationData) in oldComplicationData) {
-                        complicationSlotsManager.setComplicationDataUpdateForScreenshot(
-                            id,
-                            complicationData,
-                            now
-                        )
-                    }
-                }
-                return screenShot
             }
 
         override fun setComplicationSlotConfigExtrasChangeCallback(
@@ -963,6 +943,7 @@
             WatchFace.unregisterEditorDelegate(componentName)
         }
         unregisterReceivers()
+        displayManager.unregisterDisplayListener(displayListener)
     }
 
     @UiThread
@@ -1178,57 +1159,26 @@
     @RequiresApi(27)
     internal fun renderWatchFaceToBitmap(params: WatchFaceRenderParams): Bundle =
         TraceEvent("WatchFaceImpl.renderWatchFaceToBitmap").use {
-            val oldStyle = currentUserStyleRepository.userStyle.value
             val instant = Instant.ofEpochMilli(params.calendarTimeMillis)
-
-            params.userStyle?.let {
-                currentUserStyleRepository.updateUserStyle(
-                    UserStyle(UserStyleData(it), currentUserStyleRepository.schema)
-                )
-            }
-
-            val oldComplicationData =
-                complicationSlotsManager.complicationSlots.values.associateBy(
-                    { it.id },
-                    { it.renderer.getData() }
-                )
-
-            params.idAndComplicationDatumWireFormats?.let {
-                for (idAndData in it) {
-                    complicationSlotsManager.setComplicationDataUpdateForScreenshot(
-                        idAndData.id,
-                        idAndData.complicationData.toApiComplicationData(),
+            val bitmap =
+                setForScreenshot(
+                        params.userStyle?.let {
+                            UserStyle(UserStyleData(it), currentUserStyleRepository.schema)
+                        },
+                        params.idAndComplicationDatumWireFormats?.let { idAndData ->
+                            idAndData.associate {
+                                it.id to it.complicationData.toApiComplicationData()
+                            }
+                        },
                         instant
                     )
-                }
-            }
-
-            val bitmap =
-                renderer.takeScreenshot(
-                    ZonedDateTime.ofInstant(instant, ZoneId.of("UTC")),
-                    RenderParameters(params.renderParametersWireFormat)
-                )
-
-            // No point in restoring the old style and complication if this is headless.
-            if (!watchState.isHeadless) {
-                // Restore previous style & complicationSlots if required.
-                if (params.userStyle != null) {
-                    currentUserStyleRepository.updateUserStyle(oldStyle)
-                }
-
-                if (params.idAndComplicationDatumWireFormats != null) {
-                    val now = getNow()
-                    for ((id, complicationData) in oldComplicationData) {
-                        complicationSlotsManager.setComplicationDataUpdateForScreenshot(
-                            id,
-                            complicationData,
-                            now
+                    .use {
+                        renderer.takeScreenshot(
+                            ZonedDateTime.ofInstant(instant, ZoneId.of("UTC")),
+                            RenderParameters(params.renderParametersWireFormat)
                         )
                     }
-                }
-            }
-
-            return SharedMemoryImage.ashmemWriteImageBundle(bitmap)
+            SharedMemoryImage.ashmemWriteImageBundle(bitmap)
         }
 
     @UiThread
@@ -1255,102 +1205,94 @@
     @RequiresApi(27)
     internal fun renderComplicationToBitmap(params: ComplicationRenderParams): Bundle? =
         TraceEvent("WatchFaceImpl.renderComplicationToBitmap").use {
-            val zonedDateTime =
-                ZonedDateTime.ofInstant(
-                    Instant.ofEpochMilli(params.calendarTimeMillis),
-                    ZoneId.of("UTC")
+            val slot = complicationSlotsManager[params.complicationSlotId] ?: return@use null
+            setForScreenshot(
+                    params.userStyle?.let {
+                        UserStyle(UserStyleData(it), currentUserStyleRepository.schema)
+                    },
+                    params.complicationData?.let {
+                        mapOf(params.complicationSlotId to it.toApiComplicationData())
+                    },
+                    Instant.ofEpochMilli(params.calendarTimeMillis)
                 )
-            return complicationSlotsManager[params.complicationSlotId]?.let {
-                val oldStyle = currentUserStyleRepository.userStyle.value
-                val instant = Instant.ofEpochMilli(params.calendarTimeMillis)
-
-                val newStyle = params.userStyle
-                if (newStyle != null) {
-                    currentUserStyleRepository.updateUserStyle(
-                        UserStyle(UserStyleData(newStyle), currentUserStyleRepository.schema)
-                    )
-                }
-
-                // 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
+                .use {
+                    val zonedDateTime =
+                        ZonedDateTime.ofInstant(
+                            Instant.ofEpochMilli(params.calendarTimeMillis),
+                            ZoneId.of("UTC")
                         )
-                    )
-
-                var prevData: ComplicationData? = null
-                val screenshotComplicationData = params.complicationData
-                if (screenshotComplicationData != null) {
-                    prevData = it.renderer.getData()
-                    complicationSlotsManager.setComplicationDataUpdateForScreenshot(
-                        params.complicationSlotId,
-                        screenshotComplicationData.toApiComplicationData(),
-                        instant
-                    )
-                }
-
-                val complicationBitmap: Bitmap
-                val picture = Picture()
-                if (Build.VERSION.SDK_INT >= 28) {
-                    it.renderer.render(
-                        picture.beginRecording(bounds.width(), bounds.height()),
-                        Rect(0, 0, bounds.width(), bounds.height()),
-                        zonedDateTime,
-                        RenderParameters(params.renderParametersWireFormat),
-                        params.complicationSlotId
-                    )
-                    picture.endRecording()
-                    complicationBitmap =
-                        Api28CreateBitmapHelper.createBitmap(
-                            picture,
-                            bounds.width(),
-                            bounds.height(),
-                            Bitmap.Config.ARGB_8888
+                    // Compute the bounds of the complication based on the display rather than
+                    // the headless renderer (which may be smaller).
+                    val bounds =
+                        slot.computeBounds(
+                            Rect(
+                                0,
+                                0,
+                                Resources.getSystem().displayMetrics.widthPixels,
+                                Resources.getSystem().displayMetrics.heightPixels
+                            )
                         )
-                } else {
-                    complicationBitmap =
-                        Bitmap.createBitmap(
-                            bounds.width(),
-                            bounds.height(),
-                            Bitmap.Config.ARGB_8888
-                        )
-                    it.renderer.render(
-                        Canvas(complicationBitmap),
-                        Rect(0, 0, bounds.width(), bounds.height()),
-                        zonedDateTime,
-                        RenderParameters(params.renderParametersWireFormat),
-                        params.complicationSlotId
-                    )
-                }
 
-                // No point in restoring the old style and complication if this is headless.
-                if (!watchState.isHeadless) {
-                    // Restore previous ComplicationData & style if required.
-                    if (prevData != null) {
-                        val now = getNow()
-                        complicationSlotsManager.setComplicationDataUpdateForScreenshot(
-                            params.complicationSlotId,
-                            prevData,
-                            now
+                    val complicationBitmap: Bitmap
+                    val picture = Picture()
+                    if (Build.VERSION.SDK_INT >= 28) {
+                        slot.renderer.render(
+                            picture.beginRecording(bounds.width(), bounds.height()),
+                            Rect(0, 0, bounds.width(), bounds.height()),
+                            zonedDateTime,
+                            RenderParameters(params.renderParametersWireFormat),
+                            params.complicationSlotId
+                        )
+                        picture.endRecording()
+                        complicationBitmap =
+                            Api28CreateBitmapHelper.createBitmap(
+                                picture,
+                                bounds.width(),
+                                bounds.height(),
+                                Bitmap.Config.ARGB_8888
+                            )
+                    } else {
+                        complicationBitmap =
+                            Bitmap.createBitmap(
+                                bounds.width(),
+                                bounds.height(),
+                                Bitmap.Config.ARGB_8888
+                            )
+                        slot.renderer.render(
+                            Canvas(complicationBitmap),
+                            Rect(0, 0, bounds.width(), bounds.height()),
+                            zonedDateTime,
+                            RenderParameters(params.renderParametersWireFormat),
+                            params.complicationSlotId
                         )
                     }
-
-                    if (newStyle != null) {
-                        currentUserStyleRepository.updateUserStyle(oldStyle)
-                    }
+                    val bundle = SharedMemoryImage.ashmemWriteImageBundle(complicationBitmap)
+                    complicationBitmap.recycle()
+                    bundle
                 }
-
-                val bundle = SharedMemoryImage.ashmemWriteImageBundle(complicationBitmap)
-                complicationBitmap.recycle()
-                bundle
-            }
         }
 
+    /** Sets the user style and complication data, and returns a restoration function. */
+    internal fun setForScreenshot(
+        userStyle: UserStyle?,
+        complicationIdToData: Map<Int, ComplicationData>?,
+        instant: Instant,
+    ): AutoCloseable {
+        val restoreUserStyle =
+            userStyle?.let { currentUserStyleRepository.updateUserStyleForScreenshot(it) }
+        try {
+            val restoreComplications =
+                complicationIdToData?.let {
+                    complicationSlotsManager.setComplicationDataForScreenshot(it, instant)
+                }
+            return AutoCloseable { restoreUserStyle?.use { restoreComplications?.close() } }
+        } catch (e: Throwable) {
+            // Cleanup on failure.
+            restoreUserStyle?.close()
+            throw e
+        }
+    }
+
     @UiThread
     internal fun dump(writer: IndentingPrintWriter) {
         writer.println("WatchFaceImpl ($componentName): ")
@@ -1417,52 +1359,27 @@
         return RemoteWatchFaceView(view, host, watchFaceHostApi.getUiThreadCoroutineScope()) {
             surfaceHolder,
             params ->
-            val oldStyle = watchFaceImpl.currentUserStyleRepository.userStyle.value
             val instant = Instant.ofEpochMilli(params.calendarTimeMillis)
-
-            params.userStyle?.let {
-                watchFaceImpl.currentUserStyleRepository.updateUserStyle(
-                    UserStyle(UserStyleData(it), watchFaceImpl.currentUserStyleRepository.schema)
+            watchFaceImpl
+                .setForScreenshot(
+                    params.userStyle?.let {
+                        UserStyle(
+                            UserStyleData(it),
+                            watchFaceImpl.currentUserStyleRepository.schema
+                        )
+                    },
+                    params.idAndComplicationDatumWireFormats?.associate {
+                        it.id to it.complicationData.toApiComplicationData()
+                    },
+                    instant
                 )
-            }
-
-            val oldComplicationData =
-                watchFaceImpl.complicationSlotsManager.complicationSlots.values.associateBy(
-                    { it.id },
-                    { it.renderer.getData() }
-                )
-
-            params.idAndComplicationDatumWireFormats?.let {
-                for (idAndData in it) {
-                    watchFaceImpl.complicationSlotsManager.setComplicationDataUpdateForScreenshot(
-                        idAndData.id,
-                        idAndData.complicationData.toApiComplicationData(),
-                        instant
+                .use {
+                    watchFaceImpl.renderer.renderScreenshotToSurface(
+                        ZonedDateTime.ofInstant(instant, ZoneId.of("UTC")),
+                        RenderParameters(params.renderParametersWireFormat),
+                        surfaceHolder
                     )
                 }
-            }
-
-            watchFaceImpl.renderer.renderScreenshotToSurface(
-                ZonedDateTime.ofInstant(instant, ZoneId.of("UTC")),
-                RenderParameters(params.renderParametersWireFormat),
-                surfaceHolder
-            )
-
-            // Restore previous style & complicationSlots if required.
-            if (params.userStyle != null) {
-                watchFaceImpl.currentUserStyleRepository.updateUserStyle(oldStyle)
-            }
-
-            if (params.idAndComplicationDatumWireFormats != null) {
-                val now = watchFaceImpl.getNow()
-                for ((id, complicationData) in oldComplicationData) {
-                    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 f131d12..e2caf60 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
@@ -57,6 +57,7 @@
 import androidx.wear.watchface.complications.ComplicationSlotBounds
 import androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy
 import androidx.wear.watchface.complications.SystemDataSources
+import androidx.wear.watchface.complications.data.ComplicationData
 import androidx.wear.watchface.complications.data.ComplicationDisplayPolicies
 import androidx.wear.watchface.complications.data.ComplicationExperimental
 import androidx.wear.watchface.complications.data.ComplicationPersistencePolicies
@@ -2860,12 +2861,9 @@
     @Test
     @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun updateComplicationData_appendsToHistory() {
-        initWallpaperInteractiveWatchFaceInstance(
-            complicationSlots = listOf(leftComplication)
-        )
+        initWallpaperInteractiveWatchFaceInstance(complicationSlots = listOf(leftComplication))
         // Validate that the history is initially empty.
-        assertThat(leftComplication.complicationHistory!!.iterator().asSequence().toList())
-            .isEmpty()
+        assertThat(leftComplication.complicationHistory!!).isEmpty()
         val longTextComplication =
             WireComplicationData.Builder(WireComplicationData.TYPE_LONG_TEXT)
                 .setLongText(WireComplicationText.plainText("TYPE_LONG_TEXT"))
@@ -2875,28 +2873,78 @@
             listOf(IdAndComplicationDataWireFormat(LEFT_COMPLICATION_ID, longTextComplication))
         )
 
-        assertThat(leftComplication.complicationHistory.toList().map { it.complicationData })
+        assertThat(leftComplication.complicationHistory.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,
+    public fun setComplicationDataUpdateForScreenshot_restoresAndDoesNotChangeHistoryOrDirtyFlag() {
+        // Arrange
+        val firstTimelineData: ComplicationData =
             WireComplicationData.Builder(WireComplicationData.TYPE_LONG_TEXT)
-                .setLongText(WireComplicationText.plainText("TYPE_LONG_TEXT"))
+                .setLongText(WireComplicationText.plainText("first timeline data"))
                 .build()
-                .toApiComplicationData(),
-            Instant.now()
+                .also {
+                    it.timelineStartEpochSecond = 1000L
+                    it.timelineEndEpochSecond = 2000L
+                }
+                .toApiComplicationData()
+        val secondTimelineData: ComplicationData =
+            WireComplicationData.Builder(WireComplicationData.TYPE_LONG_TEXT)
+                .setLongText(WireComplicationText.plainText("second timeline data"))
+                .build()
+                .also {
+                    it.timelineStartEpochSecond = 2000L
+                    it.timelineEndEpochSecond = 3000L
+                }
+                .toApiComplicationData()
+        val wrapperTimelineData: ComplicationData =
+            WireComplicationData.Builder(WireComplicationData.TYPE_NO_DATA)
+                .build()
+                .also {
+                    it.timelineStartEpochSecond = 0L
+                    it.timelineEndEpochSecond = 1000L
+                    it.setTimelineEntryCollection(
+                        listOf(
+                            firstTimelineData.asWireComplicationData(),
+                            secondTimelineData.asWireComplicationData(),
+                        )
+                    )
+                }
+                .toApiComplicationData()
+        val screenshotData: ComplicationData =
+            WireComplicationData.Builder(WireComplicationData.TYPE_LONG_TEXT)
+                .setLongText(WireComplicationText.plainText("screenshot"))
+                .build()
+                .toApiComplicationData()
+        initWallpaperInteractiveWatchFaceInstance(complicationSlots = listOf(leftComplication))
+        complicationSlotsManager.onComplicationDataUpdate(
+            leftComplication.id,
+            wrapperTimelineData,
+            Instant.ofEpochSecond(1000)
         )
+        leftComplication.dataDirty = false
 
-        assertThat(leftComplication.complicationHistory!!.iterator().asSequence().toList())
-            .isEmpty()
+        // Act
+        val actualScreenshotData =
+            complicationSlotsManager
+                .setComplicationDataForScreenshot(
+                    mapOf(LEFT_COMPLICATION_ID to screenshotData),
+                    Instant.ofEpochSecond(4000) // Also restored.
+                )
+                .use { leftComplication.complicationData.value }
+
+        // Assert
+        assertThat(actualScreenshotData).isEqualTo(screenshotData)
+        assertThat(leftComplication.complicationData.value).isEqualTo(firstTimelineData)
+        // History and dirty flag unchanged for screenshots
+        assertThat(leftComplication.complicationHistory!!.map { it.complicationData })
+            .containsExactly(wrapperTimelineData)
+        assertThat(leftComplication.dataDirty).isFalse()
+        // Timeline preserved
+        complicationSlotsManager.selectComplicationDataForInstant(Instant.ofEpochSecond(2000L))
+        assertThat(leftComplication.complicationData.value).isEqualTo(secondTimelineData)
     }
 
     @Test
diff --git a/webkit/integration-tests/testapp/lint-baseline.xml b/webkit/integration-tests/testapp/lint-baseline.xml
index f050d14..f1e21ab 100644
--- a/webkit/integration-tests/testapp/lint-baseline.xml
+++ b/webkit/integration-tests/testapp/lint-baseline.xml
@@ -1,23 +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">
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="WebViewFeature.DOCUMENT_START_SCRIPT can only be accessed from within the same library group (referenced groupId=`androidx.webkit` from groupId=`androidx.webkit.integration-tests`)"
-        errorLine1="        if (!WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) {"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/androidx/webkit/DocumentStartJavaScriptActivity.java"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="WebViewCompat.addDocumentStartJavaScript can only be called from within the same library group (referenced groupId=`androidx.webkit` from groupId=`androidx.webkit.integration-tests`)"
-        errorLine1="        WebViewCompat.addDocumentStartJavaScript(webView, jsCode, allowedOriginRules);"
-        errorLine2="                      ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/com/example/androidx/webkit/DocumentStartJavaScriptActivity.java"/>
-    </issue>
+<issues format="6" by="lint 8.3.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
 
     <issue
         id="RestrictedApiAndroidX"
diff --git a/window/window/lint-baseline.xml b/window/window/lint-baseline.xml
index 848181a..d1c7f8d 100644
--- a/window/window/lint-baseline.xml
+++ b/window/window/lint-baseline.xml
@@ -1,32 +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">
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="WindowExtensions.VENDOR_API_LEVEL_3 can only be accessed from within the same library group (referenced groupId=`androidx.window.extensions` from groupId=`androidx.window`)"
-        errorLine1="    return ExtensionsUtil.safeVendorApiLevel >= WindowExtensions.VENDOR_API_LEVEL_3"
-        errorLine2="                                                                 ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/window/embedding/ActivityEmbeddingOptions.kt"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="WindowExtensions.VENDOR_API_LEVEL_1 can only be accessed from within the same library group (referenced groupId=`androidx.window.extensions` from groupId=`androidx.window`)"
-        errorLine1="            WindowExtensions.VENDOR_API_LEVEL_1 -> api1Impl.translateCompat(splitInfo)"
-        errorLine2="                             ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/window/embedding/EmbeddingAdapter.kt"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="WindowExtensions.VENDOR_API_LEVEL_2 can only be accessed from within the same library group (referenced groupId=`androidx.window.extensions` from groupId=`androidx.window`)"
-        errorLine1="            WindowExtensions.VENDOR_API_LEVEL_2 -> api2Impl.translateCompat(splitInfo)"
-        errorLine2="                             ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/window/embedding/EmbeddingAdapter.kt"/>
-    </issue>
+<issues format="6" by="lint 8.3.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha02)" variant="all" version="8.3.0-alpha02">
 
     <issue
         id="RestrictedApiAndroidX"
@@ -67,82 +40,10 @@
     <issue
         id="RestrictedApiAndroidX"
         message="WindowExtensions.VENDOR_API_LEVEL_2 can only be accessed from within the same library group (referenced groupId=`androidx.window.extensions` from groupId=`androidx.window`)"
-        errorLine1="        if (vendorApiLevel &lt; WindowExtensions.VENDOR_API_LEVEL_2) {"
-        errorLine2="                                              ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/window/embedding/EmbeddingAdapter.kt"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="WindowExtensions.VENDOR_API_LEVEL_2 can only be accessed from within the same library group (referenced groupId=`androidx.window.extensions` from groupId=`androidx.window`)"
-        errorLine1="        require(vendorApiLevel >= WindowExtensions.VENDOR_API_LEVEL_2)"
-        errorLine2="                                                   ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/window/embedding/EmbeddingAdapter.kt"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="WindowExtensions.VENDOR_API_LEVEL_2 can only be accessed from within the same library group (referenced groupId=`androidx.window.extensions` from groupId=`androidx.window`)"
-        errorLine1="        require(vendorApiLevel >= WindowExtensions.VENDOR_API_LEVEL_2)"
-        errorLine2="                                                   ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/window/embedding/EmbeddingAdapter.kt"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="WindowExtensions.VENDOR_API_LEVEL_2 can only be accessed from within the same library group (referenced groupId=`androidx.window.extensions` from groupId=`androidx.window`)"
-        errorLine1="        if (vendorApiLevel &lt; WindowExtensions.VENDOR_API_LEVEL_2) {"
-        errorLine2="                                              ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/window/embedding/EmbeddingAdapter.kt"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="WindowExtensions.VENDOR_API_LEVEL_2 can only be accessed from within the same library group (referenced groupId=`androidx.window.extensions` from groupId=`androidx.window`)"
-        errorLine1="        if (vendorApiLevel &lt; WindowExtensions.VENDOR_API_LEVEL_2) {"
-        errorLine2="                                              ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/window/embedding/EmbeddingAdapter.kt"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="WindowExtensions.VENDOR_API_LEVEL_2 can only be accessed from within the same library group (referenced groupId=`androidx.window.extensions` from groupId=`androidx.window`)"
         errorLine1="        if (ExtensionsUtil.safeVendorApiLevel &lt; VENDOR_API_LEVEL_2) {"
         errorLine2="                                                ~~~~~~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/window/embedding/EmbeddingCompat.kt"/>
     </issue>
 
-    <issue
-        id="RestrictedApiAndroidX"
-        message="WindowExtensions.VENDOR_API_LEVEL_2 can only be accessed from within the same library group (referenced groupId=`androidx.window.extensions` from groupId=`androidx.window`)"
-        errorLine1="        ExtensionsUtil.safeVendorApiLevel >= VENDOR_API_LEVEL_2"
-        errorLine2="                                             ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/window/embedding/EmbeddingCompat.kt"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="WindowExtensions.VENDOR_API_LEVEL_3 can only be accessed from within the same library group (referenced groupId=`androidx.window.extensions` from groupId=`androidx.window`)"
-        errorLine1="        ExtensionsUtil.safeVendorApiLevel >= VENDOR_API_LEVEL_3"
-        errorLine2="                                             ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/window/embedding/EmbeddingCompat.kt"/>
-    </issue>
-
-    <issue
-        id="RestrictedApiAndroidX"
-        message="WindowExtensions.VENDOR_API_LEVEL_3 can only be accessed from within the same library group (referenced groupId=`androidx.window.extensions` from groupId=`androidx.window`)"
-        errorLine1="        ExtensionsUtil.safeVendorApiLevel >= VENDOR_API_LEVEL_3"
-        errorLine2="                                             ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/window/embedding/EmbeddingCompat.kt"/>
-    </issue>
-
 </issues>
diff --git a/work/integration-tests/testapp/build.gradle b/work/integration-tests/testapp/build.gradle
index c0384de..bdf570fa 100644
--- a/work/integration-tests/testapp/build.gradle
+++ b/work/integration-tests/testapp/build.gradle
@@ -14,13 +14,6 @@
  * limitations under the License.
  */
 
-buildscript {
-    // TODO: Remove this when this test app no longer depends on 1.0.0 of vectordrawable-animated.
-    // vectordrawable and vectordrawable-animated were accidentally using the same package name
-    // which is no longer valid in namespaced resource world.
-    project.ext["android.uniquePackageNames"] = false
-}
-
 plugins {
     id("AndroidXPlugin")
     id("com.android.application")
diff --git a/work/work-runtime/src/main/java/androidx/work/WorkRequest.kt b/work/work-runtime/src/main/java/androidx/work/WorkRequest.kt
index d2bc7a7..fa2c304 100644
--- a/work/work-runtime/src/main/java/androidx/work/WorkRequest.kt
+++ b/work/work-runtime/src/main/java/androidx/work/WorkRequest.kt
@@ -87,7 +87,7 @@
         /**
          * Sets the backoff policy and backoff delay for the work.  The default values are
          * [BackoffPolicy.EXPONENTIAL] and
-         * {@value WorkRequest#DEFAULT_BACKOFF_DELAY_MILLIS}, respectively.  `backoffDelay`
+         * [WorkRequest#DEFAULT_BACKOFF_DELAY_MILLIS], respectively.  `backoffDelay`
          * will be clamped between [WorkRequest.MIN_BACKOFF_MILLIS] and
          * [WorkRequest.MAX_BACKOFF_MILLIS].
          *
@@ -110,7 +110,7 @@
         /**
          * Sets the backoff policy and backoff delay for the work.  The default values are
          * [BackoffPolicy.EXPONENTIAL] and
-         * {@value WorkRequest#DEFAULT_BACKOFF_DELAY_MILLIS}, respectively.  `duration` will
+         * [WorkRequest#DEFAULT_BACKOFF_DELAY_MILLIS], respectively.  `duration` will
          * be clamped between [WorkRequest.MIN_BACKOFF_MILLIS] and
          * [WorkRequest.MAX_BACKOFF_MILLIS].
          *