Merge "Support for help links in Startup Insights" into androidx-main
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 097d831..420b683 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -3,15 +3,19 @@
     <option name="myName" value="Project Default" />
     <inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
       <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
     </inspection_tool>
     <inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
       <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
     </inspection_tool>
     <inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
       <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
     </inspection_tool>
     <inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
       <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
     </inspection_tool>
     <inspection_tool class="DeprecatedIsStillUsed" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="Deprecation" enabled="true" level="WARNING" enabled_by_default="true">
@@ -119,18 +123,23 @@
     </inspection_tool>
     <inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
       <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
     </inspection_tool>
     <inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
       <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
     </inspection_tool>
     <inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
       <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
     </inspection_tool>
     <inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
       <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
     </inspection_tool>
     <inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
       <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
     </inspection_tool>
     <inspection_tool class="PrivatePropertyName" enabled="true" level="WEAK WARNING" enabled_by_default="true">
       <scope name="Compose" level="WEAK WARNING" enabled="true">
diff --git a/activity/activity-compose-lint/build.gradle b/activity/activity-compose-lint/build.gradle
index f60b53f..354b82a 100644
--- a/activity/activity-compose-lint/build.gradle
+++ b/activity/activity-compose-lint/build.gradle
@@ -44,7 +44,7 @@
     testImplementation(libs.kotlinStdlib)
     testRuntimeOnly(libs.kotlinReflect)
     testImplementation(libs.kotlinStdlibJdk8)
-    testImplementation(libs.androidLintApi)
+    testImplementation(libs.androidLintPrevApi)
     testImplementation(libs.androidLintTests)
     testImplementation(libs.junit)
     testImplementation(libs.truth)
diff --git a/activity/activity-lint/build.gradle b/activity/activity-lint/build.gradle
index 955e21f..e181339 100644
--- a/activity/activity-lint/build.gradle
+++ b/activity/activity-lint/build.gradle
@@ -38,7 +38,7 @@
     testImplementation(libs.kotlinStdlib)
     testRuntimeOnly(libs.kotlinReflect)
     testImplementation(libs.kotlinStdlibJdk8)
-    testImplementation(libs.androidLintApi)
+    testImplementation(libs.androidLintPrevApi)
     testImplementation(libs.androidLintTests)
     testImplementation(libs.junit)
     testImplementation(libs.truth)
diff --git a/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/ExperimentalIssueRegistry.kt b/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/ExperimentalIssueRegistry.kt
index 4efa16b..b9461f5 100644
--- a/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/ExperimentalIssueRegistry.kt
+++ b/annotation/annotation-experimental-lint/src/main/java/androidx/annotation/experimental/lint/ExperimentalIssueRegistry.kt
@@ -23,7 +23,7 @@
 @Suppress("UnstableApiUsage")
 class ExperimentalIssueRegistry : IssueRegistry() {
     override val minApi = CURRENT_API
-    override val api = 14
+    override val api = 16
     override val issues
         get() = ExperimentalDetector.ISSUES + AnnotationRetentionDetector.ISSUES
 
diff --git a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/ApiLintVersionsTest.kt b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/ApiLintVersionsTest.kt
index cae2973..447a5c2 100644
--- a/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/ApiLintVersionsTest.kt
+++ b/annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/ApiLintVersionsTest.kt
@@ -36,6 +36,6 @@
         // We hardcode version registry.api to the version that is used to run tests.
         assertEquals("registry.api matches version used to run tests", CURRENT_API, registry.api)
         // Intentionally fails in IDE, because we use different API version in Studio and CLI.
-        assertEquals("registry.minApi matches the current API", CURRENT_API, registry.minApi)
+        assertEquals("registry.minApi matches the current API", 16, registry.minApi)
     }
 }
diff --git a/annotation/annotation/bcv/native/current.txt b/annotation/annotation/bcv/native/current.txt
index f1fd8ff..c70d512 100644
--- a/annotation/annotation/bcv/native/current.txt
+++ b/annotation/annotation/bcv/native/current.txt
@@ -131,6 +131,9 @@
         enum entry SUBCLASSES // androidx.annotation/RestrictTo.Scope.SUBCLASSES|null[0]
         enum entry TESTS // androidx.annotation/RestrictTo.Scope.TESTS|null[0]
 
+        final val entries // androidx.annotation/RestrictTo.Scope.entries|#static{}entries[0]
+            final fun <get-entries>(): kotlin.enums/EnumEntries<androidx.annotation/RestrictTo.Scope> // androidx.annotation/RestrictTo.Scope.entries.<get-entries>|<get-entries>#static(){}[0]
+
         final fun valueOf(kotlin/String): androidx.annotation/RestrictTo.Scope // androidx.annotation/RestrictTo.Scope.valueOf|valueOf#static(kotlin.String){}[0]
         final fun values(): kotlin/Array<androidx.annotation/RestrictTo.Scope> // androidx.annotation/RestrictTo.Scope.values|values#static(){}[0]
     }
diff --git a/annotation/annotation/build.gradle b/annotation/annotation/build.gradle
index 043e852..c8681d9 100644
--- a/annotation/annotation/build.gradle
+++ b/annotation/annotation/build.gradle
@@ -42,9 +42,6 @@
 
         wasmJsMain {
             dependsOn(nonJvmMain)
-            dependencies {
-                implementation(libs.kotlinStdlibJs)
-            }
         }
 
         targets.configureEach { target ->
@@ -89,7 +86,7 @@
     name = "Annotation"
     type = LibraryType.PUBLISHED_LIBRARY
     mavenVersion = LibraryVersions.ANNOTATION
-    kotlinTarget = KotlinTarget.KOTLIN_1_7
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
     inceptionYear = "2013"
     description = "Provides source annotations for tooling and readability."
 }
diff --git a/appcompat/appcompat-lint/src/main/kotlin/androidx/appcompat/AppCompatIssueRegistry.kt b/appcompat/appcompat-lint/src/main/kotlin/androidx/appcompat/AppCompatIssueRegistry.kt
index ec5c2f3..e9c8adc 100644
--- a/appcompat/appcompat-lint/src/main/kotlin/androidx/appcompat/AppCompatIssueRegistry.kt
+++ b/appcompat/appcompat-lint/src/main/kotlin/androidx/appcompat/AppCompatIssueRegistry.kt
@@ -33,7 +33,7 @@
 @Suppress("UnstableApiUsage")
 class AppCompatIssueRegistry : IssueRegistry() {
     override val minApi = CURRENT_API
-    override val api = 14
+    override val api = 16
     override val issues
         get() =
             listOf(
diff --git a/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/ApiLintVersionsTest.kt b/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/ApiLintVersionsTest.kt
index 69d79d8..8ab42db 100644
--- a/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/ApiLintVersionsTest.kt
+++ b/appcompat/appcompat-lint/src/test/kotlin/androidx/appcompat/lint/ApiLintVersionsTest.kt
@@ -37,6 +37,6 @@
         // We hardcode version registry.api to the version that is used to run tests.
         assertEquals("registry.api matches version used to run tests", CURRENT_API, registry.api)
         // Intentionally fails in IDE, because we use different API version in Studio and CLI.
-        assertEquals("registry.minApi is set to minimum level of 14", 14, registry.minApi)
+        assertEquals("registry.minApi is set to minimum level of 16", 16, registry.minApi)
     }
 }
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java
index ae89b19..1c56469 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchImplTest.java
@@ -3177,11 +3177,16 @@
                     }
 
                     @Override
-                    public int getMaxDocumentCount() {
+                    public int getPerPackageDocumentCountLimit() {
                         return 1;
                     }
 
                     @Override
+                    public int getDocumentCountLimitStartThreshold() {
+                        return 0;
+                    }
+
+                    @Override
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
@@ -3256,11 +3261,16 @@
                     }
 
                     @Override
-                    public int getMaxDocumentCount() {
+                    public int getPerPackageDocumentCountLimit() {
                         return 1;
                     }
 
                     @Override
+                    public int getDocumentCountLimitStartThreshold() {
+                        return 0;
+                    }
+
+                    @Override
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
@@ -3313,11 +3323,16 @@
                     }
 
                     @Override
-                    public int getMaxDocumentCount() {
+                    public int getPerPackageDocumentCountLimit() {
                         return 1;
                     }
 
                     @Override
+                    public int getDocumentCountLimitStartThreshold() {
+                        return 0;
+                    }
+
+                    @Override
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
@@ -3350,11 +3365,16 @@
                     }
 
                     @Override
-                    public int getMaxDocumentCount() {
+                    public int getPerPackageDocumentCountLimit() {
                         return 3;
                     }
 
                     @Override
+                    public int getDocumentCountLimitStartThreshold() {
+                        return 0;
+                    }
+
+                    @Override
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
@@ -3463,11 +3483,16 @@
                     }
 
                     @Override
-                    public int getMaxDocumentCount() {
+                    public int getPerPackageDocumentCountLimit() {
                         return 2;
                     }
 
                     @Override
+                    public int getDocumentCountLimitStartThreshold() {
+                        return 0;
+                    }
+
+                    @Override
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
@@ -3559,11 +3584,16 @@
                     }
 
                     @Override
-                    public int getMaxDocumentCount() {
+                    public int getPerPackageDocumentCountLimit() {
                         return 2;
                     }
 
                     @Override
+                    public int getDocumentCountLimitStartThreshold() {
+                        return 0;
+                    }
+
+                    @Override
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
@@ -3616,11 +3646,16 @@
                     }
 
                     @Override
-                    public int getMaxDocumentCount() {
+                    public int getPerPackageDocumentCountLimit() {
                         return 3;
                     }
 
                     @Override
+                    public int getDocumentCountLimitStartThreshold() {
+                        return 0;
+                    }
+
+                    @Override
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
@@ -3769,11 +3804,16 @@
                     }
 
                     @Override
-                    public int getMaxDocumentCount() {
+                    public int getPerPackageDocumentCountLimit() {
                         return 2;
                     }
 
                     @Override
+                    public int getDocumentCountLimitStartThreshold() {
+                        return 0;
+                    }
+
+                    @Override
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
@@ -3852,11 +3892,16 @@
                     }
 
                     @Override
-                    public int getMaxDocumentCount() {
+                    public int getPerPackageDocumentCountLimit() {
                         return 2;
                     }
 
                     @Override
+                    public int getDocumentCountLimitStartThreshold() {
+                        return 0;
+                    }
+
+                    @Override
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
@@ -3909,11 +3954,16 @@
                     }
 
                     @Override
-                    public int getMaxDocumentCount() {
+                    public int getPerPackageDocumentCountLimit() {
                         return 2;
                     }
 
                     @Override
+                    public int getDocumentCountLimitStartThreshold() {
+                        return 0;
+                    }
+
+                    @Override
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
@@ -3943,6 +3993,7 @@
         assertThat(e).hasMessageThat().contains(
                 "Package \"package\" exceeded limit of 2 documents");
     }
+
     @Test
     public void testLimitConfig_suggestion() throws Exception {
         mAppSearchImpl.close();
@@ -3955,11 +4006,16 @@
                     }
 
                     @Override
-                    public int getMaxDocumentCount() {
+                    public int getPerPackageDocumentCountLimit() {
                         return Integer.MAX_VALUE;
                     }
 
                     @Override
+                    public int getDocumentCountLimitStartThreshold() {
+                        return 0;
+                    }
+
+                    @Override
                     public int getMaxSuggestionCount() {
                         return 2;
                     }
@@ -3978,6 +4034,502 @@
                 "Trying to get 10 suggestion results, which exceeds limit of 2");
     }
 
+    @Test
+    public void testLimitConfig_belowLimitStartThreshold_limitHasNoEffect() throws Exception {
+        // Create a new mAppSearchImpl with a low limit, but a higher limit start threshold.
+        mAppSearchImpl.close();
+        File tempFolder = mTemporaryFolder.newFolder();
+        mAppSearchImpl = AppSearchImpl.create(
+                tempFolder, new AppSearchConfigImpl(new LimitConfig() {
+                    @Override
+                    public int getMaxDocumentSizeBytes() {
+                        return Integer.MAX_VALUE;
+                    }
+
+                    @Override
+                    public int getPerPackageDocumentCountLimit() {
+                        return 1;
+                    }
+
+                    @Override
+                    public int getDocumentCountLimitStartThreshold() {
+                        return 3;
+                    }
+
+                    @Override
+                    public int getMaxSuggestionCount() {
+                        return Integer.MAX_VALUE;
+                    }
+                }, new LocalStorageIcingOptionsConfig()),
+                /*initStatsBuilder=*/ null, /*visibilityChecker=*/ null,
+                ALWAYS_OPTIMIZE);
+
+        // Insert schema
+        List<AppSearchSchema> schemas =
+                Collections.singletonList(new AppSearchSchema.Builder("type").build());
+        InternalSetSchemaResponse internalSetSchemaResponse = mAppSearchImpl.setSchema(
+                "package",
+                "database",
+                schemas,
+                /*visibilityConfigs=*/ Collections.emptyList(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0,
+                /* setSchemaStatsBuilder= */ null);
+        assertThat(internalSetSchemaResponse.isSuccess()).isTrue();
+
+        // Index a document
+        mAppSearchImpl.putDocument(
+                "package",
+                "database",
+                new GenericDocument.Builder<>("namespace", "id1", "type").build(),
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+
+        // We should still be able to index another document even though we are over the
+        // getPerPackageDocumentCountLimit threshold.
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("namespace", "id2", "type").build();
+        mAppSearchImpl.putDocument(
+                "package", "database", document2, /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+    }
+
+    @Test
+    public void testLimitConfig_aboveLimitStartThreshold_limitTakesEffect() throws Exception {
+        // Create a new mAppSearchImpl with a low limit, but a higher limit start threshold.
+        mAppSearchImpl.close();
+        File tempFolder = mTemporaryFolder.newFolder();
+        mAppSearchImpl = AppSearchImpl.create(
+                tempFolder, new AppSearchConfigImpl(new LimitConfig() {
+                    @Override
+                    public int getMaxDocumentSizeBytes() {
+                        return Integer.MAX_VALUE;
+                    }
+
+                    @Override
+                    public int getPerPackageDocumentCountLimit() {
+                        return 1;
+                    }
+
+                    @Override
+                    public int getDocumentCountLimitStartThreshold() {
+                        return 3;
+                    }
+
+                    @Override
+                    public int getMaxSuggestionCount() {
+                        return Integer.MAX_VALUE;
+                    }
+                }, new LocalStorageIcingOptionsConfig()),
+                /*initStatsBuilder=*/ null, /*visibilityChecker=*/ null,
+                ALWAYS_OPTIMIZE);
+
+        // Insert schemas for thress packages
+        List<AppSearchSchema> schemas =
+                Collections.singletonList(new AppSearchSchema.Builder("type").build());
+        InternalSetSchemaResponse internalSetSchemaResponse = mAppSearchImpl.setSchema(
+                "package",
+                "database",
+                schemas,
+                /*visibilityConfigs=*/ Collections.emptyList(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0,
+                /* setSchemaStatsBuilder= */ null);
+        assertThat(internalSetSchemaResponse.isSuccess()).isTrue();
+        internalSetSchemaResponse = mAppSearchImpl.setSchema(
+                "package2",
+                "database",
+                schemas,
+                /*visibilityConfigs=*/ Collections.emptyList(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0,
+                /* setSchemaStatsBuilder= */ null);
+        assertThat(internalSetSchemaResponse.isSuccess()).isTrue();
+        internalSetSchemaResponse = mAppSearchImpl.setSchema(
+                "package3",
+                "database",
+                schemas,
+                /*visibilityConfigs=*/ Collections.emptyList(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0,
+                /* setSchemaStatsBuilder= */ null);
+        assertThat(internalSetSchemaResponse.isSuccess()).isTrue();
+
+        // Index a document
+        mAppSearchImpl.putDocument(
+                "package",
+                "database",
+                new GenericDocument.Builder<>("namespace", "id1", "type").build(),
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+
+        // We should still be able to index another document even though we are over the
+        // getPerPackageDocumentCountLimit threshold.
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("namespace", "id2", "type").build();
+        mAppSearchImpl.putDocument(
+                "package", "database", document2, /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+
+        // Index a document in another package. We will now be at the limit start threshold.
+        GenericDocument document3 =
+                new GenericDocument.Builder<>("namespace", "id3", "type").build();
+        mAppSearchImpl.putDocument(
+                "package2", "database", document3, /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+
+        // Both packages are at the maxPerPackageDocumentLimitCount and the limit is in force.
+        // Neither should be able to add another document.
+        GenericDocument document4 =
+                new GenericDocument.Builder<>("namespace", "id4", "type").build();
+        AppSearchException e = assertThrows(AppSearchException.class, () ->
+                mAppSearchImpl.putDocument(
+                        "package",
+                        "database",
+                        document4,
+                        /*sendChangeNotifications=*/ false,
+                        /*logger=*/ null));
+        assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
+        assertThat(e).hasMessageThat().contains(
+                "Package \"package\" exceeded limit of 1 documents");
+
+        e = assertThrows(AppSearchException.class, () ->
+                mAppSearchImpl.putDocument(
+                        "package2",
+                        "database",
+                        document4,
+                        /*sendChangeNotifications=*/ false,
+                        /*logger=*/ null));
+        assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
+        assertThat(e).hasMessageThat().contains(
+                "Package \"package2\" exceeded limit of 1 documents");
+
+        // A new package should still be able to add a document however.
+        mAppSearchImpl.putDocument(
+                "package3", "database", document4, /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+    }
+
+    @Test
+    public void testLimitConfig_replacement_doesntTriggerLimitStartThreshold() throws Exception {
+        // Create a new mAppSearchImpl with a low limit, but a higher limit start threshold.
+        mAppSearchImpl.close();
+        File tempFolder = mTemporaryFolder.newFolder();
+        mAppSearchImpl = AppSearchImpl.create(
+                tempFolder, new AppSearchConfigImpl(new LimitConfig() {
+                    @Override
+                    public int getMaxDocumentSizeBytes() {
+                        return Integer.MAX_VALUE;
+                    }
+
+                    @Override
+                    public int getPerPackageDocumentCountLimit() {
+                        return 1;
+                    }
+
+                    @Override
+                    public int getDocumentCountLimitStartThreshold() {
+                        return 3;
+                    }
+
+                    @Override
+                    public int getMaxSuggestionCount() {
+                        return Integer.MAX_VALUE;
+                    }
+                }, new LocalStorageIcingOptionsConfig()),
+                /*initStatsBuilder=*/ null, /*visibilityChecker=*/ null,
+                ALWAYS_OPTIMIZE);
+
+        // Insert schema
+        List<AppSearchSchema> schemas =
+                Collections.singletonList(new AppSearchSchema.Builder("type").build());
+        InternalSetSchemaResponse internalSetSchemaResponse = mAppSearchImpl.setSchema(
+                "package",
+                "database",
+                schemas,
+                /*visibilityConfigs=*/ Collections.emptyList(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0,
+                /* setSchemaStatsBuilder= */ null);
+        assertThat(internalSetSchemaResponse.isSuccess()).isTrue();
+
+        // Index two documents
+        mAppSearchImpl.putDocument(
+                "package",
+                "database",
+                new GenericDocument.Builder<>("namespace", "id1", "type").build(),
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("namespace", "id2", "type").build();
+        mAppSearchImpl.putDocument(
+                "package", "database", document2, /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+
+        // Now Index a replacement. This should not trigger the DocumentCountLimitStartThreshold
+        // because the total number of living documents should still be two.
+        mAppSearchImpl.putDocument(
+                "package", "database", document2, /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+
+        // We should be able to index one more document before triggering the limit.
+        GenericDocument document3 =
+                new GenericDocument.Builder<>("namespace", "id3", "type").build();
+        mAppSearchImpl.putDocument(
+                "package", "database", document3, /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+    }
+
+    @Test
+    public void testLimitConfig_remove_deactivatesDocumentCountLimit() throws Exception {
+        // Create a new mAppSearchImpl with a low limit, but a higher limit start threshold.
+        mAppSearchImpl.close();
+        File tempFolder = mTemporaryFolder.newFolder();
+        mAppSearchImpl = AppSearchImpl.create(
+                tempFolder, new AppSearchConfigImpl(new LimitConfig() {
+                    @Override
+                    public int getMaxDocumentSizeBytes() {
+                        return Integer.MAX_VALUE;
+                    }
+
+                    @Override
+                    public int getPerPackageDocumentCountLimit() {
+                        return 1;
+                    }
+
+                    @Override
+                    public int getDocumentCountLimitStartThreshold() {
+                        return 3;
+                    }
+
+                    @Override
+                    public int getMaxSuggestionCount() {
+                        return Integer.MAX_VALUE;
+                    }
+                }, new LocalStorageIcingOptionsConfig()),
+                /*initStatsBuilder=*/ null, /*visibilityChecker=*/ null,
+                ALWAYS_OPTIMIZE);
+
+        // Insert schema
+        List<AppSearchSchema> schemas =
+                Collections.singletonList(new AppSearchSchema.Builder("type").build());
+        InternalSetSchemaResponse internalSetSchemaResponse = mAppSearchImpl.setSchema(
+                "package",
+                "database",
+                schemas,
+                /*visibilityConfigs=*/ Collections.emptyList(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0,
+                /* setSchemaStatsBuilder= */ null);
+        assertThat(internalSetSchemaResponse.isSuccess()).isTrue();
+        internalSetSchemaResponse = mAppSearchImpl.setSchema(
+                "package2",
+                "database",
+                schemas,
+                /*visibilityConfigs=*/ Collections.emptyList(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0,
+                /* setSchemaStatsBuilder= */ null);
+        assertThat(internalSetSchemaResponse.isSuccess()).isTrue();
+
+        // Index three documents in "package" and one in "package2". This will mean four total
+        // documents in the system which will exceed the limit start threshold of three. The limit
+        // will be in force and neither package will be able to documents.
+        mAppSearchImpl.putDocument(
+                "package",
+                "database",
+                new GenericDocument.Builder<>("namespace", "id1", "type").build(),
+                /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("namespace", "id2", "type").build();
+        mAppSearchImpl.putDocument(
+                "package", "database", document2, /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+
+        GenericDocument document3 =
+                new GenericDocument.Builder<>("namespace", "id3", "type").build();
+        mAppSearchImpl.putDocument(
+                "package", "database", document3, /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+
+        GenericDocument document4 =
+                new GenericDocument.Builder<>("namespace", "id4", "type").build();
+        mAppSearchImpl.putDocument(
+                "package2", "database", document4, /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+
+        // The limit is in force. We should be unable to index another document. Even after we
+        // delete one document, the system is still over the limit start threshold.
+        GenericDocument document5 =
+                new GenericDocument.Builder<>("namespace", "id5", "type").build();
+        AppSearchException e = assertThrows(AppSearchException.class, () ->
+                mAppSearchImpl.putDocument(
+                        "package",
+                        "database",
+                        document5,
+                        /*sendChangeNotifications=*/ false,
+                        /*logger=*/ null));
+        assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
+        assertThat(e).hasMessageThat().contains(
+                "Package \"package\" exceeded limit of 1 documents");
+
+        mAppSearchImpl.remove(
+                "package", "database", "namespace", "id2", /*removeStatsBuilder=*/null);
+        e = assertThrows(AppSearchException.class, () ->
+                mAppSearchImpl.putDocument(
+                        "package",
+                        "database",
+                        document5,
+                        /*sendChangeNotifications=*/ false,
+                        /*logger=*/ null));
+        assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
+        assertThat(e).hasMessageThat().contains(
+                "Package \"package\" exceeded limit of 1 documents");
+
+        // Removing another document will bring the system below the limit start threshold. Now,
+        // adding another document can succeed.
+        mAppSearchImpl.remove(
+                "package", "database", "namespace", "id3", /*removeStatsBuilder=*/null);
+        mAppSearchImpl.putDocument(
+                "package", "database", document5, /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+    }
+
+    @Test
+    public void testLimitConfig_removeByQuery_deactivatesDocumentCountLimit() throws Exception {
+        // Create a new mAppSearchImpl with a low limit, but a higher limit start threshold.
+        mAppSearchImpl.close();
+        File tempFolder = mTemporaryFolder.newFolder();
+        mAppSearchImpl = AppSearchImpl.create(
+                tempFolder, new AppSearchConfigImpl(new LimitConfig() {
+                    @Override
+                    public int getMaxDocumentSizeBytes() {
+                        return Integer.MAX_VALUE;
+                    }
+
+                    @Override
+                    public int getPerPackageDocumentCountLimit() {
+                        return 1;
+                    }
+
+                    @Override
+                    public int getDocumentCountLimitStartThreshold() {
+                        return 3;
+                    }
+
+                    @Override
+                    public int getMaxSuggestionCount() {
+                        return Integer.MAX_VALUE;
+                    }
+                }, new LocalStorageIcingOptionsConfig()),
+                /*initStatsBuilder=*/ null, /*visibilityChecker=*/ null,
+                ALWAYS_OPTIMIZE);
+
+        // Insert schema
+        AppSearchSchema schema =
+                new AppSearchSchema.Builder("type")
+                        .addProperty(
+                                new AppSearchSchema.StringPropertyConfig.Builder("number")
+                                        .setIndexingType(
+                                                AppSearchSchema.StringPropertyConfig.
+                                                        INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(
+                                                AppSearchSchema.StringPropertyConfig.
+                                                        TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .addProperty(
+                                new AppSearchSchema.StringPropertyConfig.Builder("evenOdd")
+                                        .setIndexingType(
+                                                AppSearchSchema.StringPropertyConfig.
+                                                        INDEXING_TYPE_PREFIXES)
+                                        .setTokenizerType(
+                                                AppSearchSchema.StringPropertyConfig.
+                                                        TOKENIZER_TYPE_PLAIN)
+                                        .build())
+                        .build();
+        List<AppSearchSchema> schemas = Collections.singletonList(schema);
+
+        InternalSetSchemaResponse internalSetSchemaResponse = mAppSearchImpl.setSchema(
+                "package",
+                "database",
+                schemas,
+                /*visibilityConfigs=*/ Collections.emptyList(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0,
+                /* setSchemaStatsBuilder= */ null);
+        assertThat(internalSetSchemaResponse.isSuccess()).isTrue();
+        internalSetSchemaResponse = mAppSearchImpl.setSchema(
+                "package2",
+                "database",
+                schemas,
+                /*visibilityConfigs=*/ Collections.emptyList(),
+                /*forceOverride=*/ false,
+                /*version=*/ 0,
+                /* setSchemaStatsBuilder= */ null);
+        assertThat(internalSetSchemaResponse.isSuccess()).isTrue();
+
+        // Index three documents in "package" and one in "package2". This will mean four total
+        // documents in the system which will exceed the limit start threshold of three. The limit
+        // will be in force and neither package will be able to documents.
+        GenericDocument document1 =
+                new GenericDocument.Builder<>("namespace", "id1", "type")
+                        .setPropertyString("number","first")
+                        .setPropertyString("evenOdd", "odd").build();
+        mAppSearchImpl.putDocument("package", "database", document1,
+                /*sendChangeNotifications=*/ false, /*logger=*/ null);
+
+        GenericDocument document2 =
+                new GenericDocument.Builder<>("namespace", "id2", "type")
+                        .setPropertyString("number","second")
+                        .setPropertyString("evenOdd", "even").build();
+        mAppSearchImpl.putDocument(
+                "package", "database", document2, /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+
+        GenericDocument document3 =
+                new GenericDocument.Builder<>("namespace", "id3", "type")
+                        .setPropertyString("number","third")
+                        .setPropertyString("evenOdd", "odd").build();
+        mAppSearchImpl.putDocument(
+                "package", "database", document3, /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+
+        GenericDocument document4 =
+                new GenericDocument.Builder<>("namespace", "id4", "type")
+                        .setPropertyString("number","fourth")
+                        .setPropertyString("evenOdd", "even").build();
+        mAppSearchImpl.putDocument(
+                "package2", "database", document4, /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+
+        // The limit is in force. We should be unable to index another document.
+        GenericDocument document5 =
+                new GenericDocument.Builder<>("namespace", "id5", "type")
+                        .setPropertyString("number","five")
+                        .setPropertyString("evenOdd", "odd").build();
+        AppSearchException e = assertThrows(AppSearchException.class, () ->
+                mAppSearchImpl.putDocument(
+                        "package",
+                        "database",
+                        document5,
+                        /*sendChangeNotifications=*/ false,
+                        /*logger=*/ null));
+        assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE);
+        assertThat(e).hasMessageThat().contains(
+                "Package \"package\" exceeded limit of 1 documents");
+
+        // Remove two documents by query. Now we should be under the limit and be able to add
+        // another document.
+        mAppSearchImpl.removeByQuery("package", "database", "evenOdd:odd",
+                new SearchSpec.Builder().build(), /*removeStatsBuilder=*/null);
+        mAppSearchImpl.putDocument(
+                "package", "database", document5, /*sendChangeNotifications=*/ false,
+                /*logger=*/ null);
+    }
+
     /**
      * Ensure that it is okay to register the same observer for multiple packages and that removing
      * the observer for one package doesn't remove it for the other.
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchConfigImpl.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchConfigImpl.java
index 639ec7db8..2537e6f 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchConfigImpl.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchConfigImpl.java
@@ -129,8 +129,13 @@
     }
 
     @Override
-    public int getMaxDocumentCount() {
-        return mLimitConfig.getMaxDocumentCount();
+    public int getPerPackageDocumentCountLimit() {
+        return mLimitConfig.getPerPackageDocumentCountLimit();
+    }
+
+    @Override
+    public int getDocumentCountLimitStartThreshold() {
+        return mLimitConfig.getDocumentCountLimitStartThreshold();
     }
 
     @Override
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java
index d175aa6..849be28 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AppSearchImpl.java
@@ -195,9 +195,9 @@
     @GuardedBy("mReadWriteLock")
     private final Map<String, Set<String>> mNamespaceMapLocked = new HashMap<>();
 
-    /** Maps package name to active document count. */
+    // Marked as volatile because a new instance may be assigned during resetLocked.
     @GuardedBy("mReadWriteLock")
-    private final Map<String, Integer> mDocumentCountMapLocked = new ArrayMap<>();
+    private volatile DocumentLimiter mDocumentLimiterLocked;
 
     // Maps packages to the set of valid nextPageTokens that the package can manipulate. A token
     // is unique and constant per query (i.e. the same token '123' is used to iterate through
@@ -388,7 +388,12 @@
                 }
 
                 // Populate document count map
-                rebuildDocumentCountMapLocked(storageInfoProto);
+                mDocumentLimiterLocked =
+                        new DocumentLimiter(
+                                mConfig.getDocumentCountLimitStartThreshold(),
+                                mConfig.getPerPackageDocumentCountLimit(),
+                                storageInfoProto.getDocumentStorageInfo()
+                                        .getNamespaceStorageInfoList());
 
                 // logging prepare_schema_and_namespaces latency
                 if (initStatsBuilder != null) {
@@ -1041,7 +1046,7 @@
             DocumentProto finalDocument = documentBuilder.build();
 
             // Check limits
-            int newDocumentCount = enforceLimitConfigLocked(
+            enforceLimitConfigLocked(
                     packageName, finalDocument.getUri(), finalDocument.getSerializedSize());
 
             // Insert document
@@ -1068,7 +1073,15 @@
 
             // Only update caches if the document is successfully put to Icing.
             addToMap(mNamespaceMapLocked, prefix, finalDocument.getNamespace());
-            mDocumentCountMapLocked.put(packageName, newDocumentCount);
+            if (!putResultProto.getWasReplacement()) {
+                // If the document was a replacement, then there is no need to report it because the
+                // number of documents has not changed. We only need to report "true" additions to
+                // the DocumentLimiter.
+                // Even replacement document will consume a document id, but the limit is only
+                // intended to apply to "living" documents. It is the responsibility of AppSearch
+                // it's optimization task to reclaim space when needed.
+                mDocumentLimiterLocked.reportDocumentAdded(packageName);
+            }
 
             // Prepare notifications
             if (sendChangeNotifications) {
@@ -1097,12 +1110,11 @@
      * Checks that a new document can be added to the given packageName with the given serialized
      * size without violating our {@link LimitConfig}.
      *
-     * @return the new count of documents for the given package, including the new document.
      * @throws AppSearchException with a code of {@link AppSearchResult#RESULT_OUT_OF_SPACE} if the
      *                            limits are violated by the new document.
      */
     @GuardedBy("mReadWriteLock")
-    private int enforceLimitConfigLocked(String packageName, String newDocUri, int newDocSize)
+    private void enforceLimitConfigLocked(String packageName, String newDocUri, int newDocSize)
             throws AppSearchException {
         // Limits check: size of document
         if (newDocSize > mConfig.getMaxDocumentSizeBytes()) {
@@ -1113,39 +1125,7 @@
                             + "limit of " + mConfig.getMaxDocumentSizeBytes() + " bytes");
         }
 
-        // Limits check: number of documents
-        Integer oldDocumentCount = mDocumentCountMapLocked.get(packageName);
-        int newDocumentCount;
-        if (oldDocumentCount == null) {
-            newDocumentCount = 1;
-        } else {
-            newDocumentCount = oldDocumentCount + 1;
-        }
-        if (newDocumentCount > mConfig.getMaxDocumentCount()) {
-            // Our management of mDocumentCountMapLocked doesn't account for document
-            // replacements, so our counter might have overcounted if the app has replaced docs.
-            // Rebuild the counter from StorageInfo in case this is so.
-            // TODO(b/170371356):  If Icing lib exposes something in the result which says
-            //  whether the document was a replacement, we could subtract 1 again after the put
-            //  to keep the count accurate. That would allow us to remove this code.
-            rebuildDocumentCountMapLocked(getRawStorageInfoProto());
-            oldDocumentCount = mDocumentCountMapLocked.get(packageName);
-            if (oldDocumentCount == null) {
-                newDocumentCount = 1;
-            } else {
-                newDocumentCount = oldDocumentCount + 1;
-            }
-        }
-        if (newDocumentCount > mConfig.getMaxDocumentCount()) {
-            // Now we really can't fit it in, even accounting for replacements.
-            throw new AppSearchException(
-                    AppSearchResult.RESULT_OUT_OF_SPACE,
-                    "Package \"" + packageName + "\" exceeded limit of "
-                            + mConfig.getMaxDocumentCount() + " documents. Some documents "
-                            + "must be removed to index additional ones.");
-        }
-
-        return newDocumentCount;
+        mDocumentLimiterLocked.enforceDocumentCountLimit(packageName);
     }
 
     /**
@@ -1861,7 +1841,7 @@
             checkSuccess(deleteResultProto.getStatus());
 
             // Update derived maps
-            updateDocumentCountAfterRemovalLocked(packageName, /*numDocumentsDeleted=*/ 1);
+            mDocumentLimiterLocked.reportDocumentsRemoved(packageName, /*numDocumentsDeleted=*/1);
 
             // Prepare notifications
             if (schemaType != null) {
@@ -2008,7 +1988,7 @@
         // Update derived maps
         int numDocumentsDeleted =
                 deleteResultProto.getDeleteByQueryStats().getNumDocumentsDeleted();
-        updateDocumentCountAfterRemovalLocked(packageName, numDocumentsDeleted);
+        mDocumentLimiterLocked.reportDocumentsRemoved(packageName, numDocumentsDeleted);
 
         if (prefixedObservedSchemas != null && !prefixedObservedSchemas.isEmpty()) {
             dispatchChangeNotificationsAfterRemoveByQueryLocked(packageName,
@@ -2017,22 +1997,6 @@
     }
 
     @GuardedBy("mReadWriteLock")
-    private void updateDocumentCountAfterRemovalLocked(
-            @NonNull String packageName, int numDocumentsDeleted) {
-        if (numDocumentsDeleted > 0) {
-            Integer oldDocumentCount = mDocumentCountMapLocked.get(packageName);
-            // This should always be true: how can we delete documents for a package without
-            // having seen that package during init? This is just a safeguard.
-            if (oldDocumentCount != null) {
-                // This should always be >0; how can we remove more documents than we've indexed?
-                // This is just a safeguard.
-                int newDocumentCount = Math.max(oldDocumentCount - numDocumentsDeleted, 0);
-                mDocumentCountMapLocked.put(packageName, newDocumentCount);
-            }
-        }
-    }
-
-    @GuardedBy("mReadWriteLock")
     private void dispatchChangeNotificationsAfterRemoveByQueryLocked(
             @NonNull String packageName,
             @NonNull DeleteByQueryResultProto deleteResultProto,
@@ -2351,7 +2315,7 @@
                 String packageName = entry.getKey();
                 Set<String> databaseNames = entry.getValue();
                 if (!installedPackages.contains(packageName) && databaseNames != null) {
-                    mDocumentCountMapLocked.remove(packageName);
+                    mDocumentLimiterLocked.reportPackageRemoved(packageName);
                     synchronized (mNextPageTokensLocked) {
                         mNextPageTokensLocked.remove(packageName);
                     }
@@ -2391,7 +2355,14 @@
         mOptimizeIntervalCountLocked = 0;
         mSchemaCacheLocked.clear();
         mNamespaceMapLocked.clear();
-        mDocumentCountMapLocked.clear();
+
+        // We just reset the index. So there is no need to retrieve the actual storage info. We know
+        // that there are no actual namespaces.
+        List<NamespaceStorageInfoProto> emptyNamespaceInfos = Collections.emptyList();
+        mDocumentLimiterLocked =
+                new DocumentLimiter(
+                        mConfig.getDocumentCountLimitStartThreshold(),
+                        mConfig.getPerPackageDocumentCountLimit(), emptyNamespaceInfos);
         synchronized (mNextPageTokensLocked) {
             mNextPageTokensLocked.clear();
         }
@@ -2404,26 +2375,6 @@
         checkSuccess(resetResultProto.getStatus());
     }
 
-    @GuardedBy("mReadWriteLock")
-    private void rebuildDocumentCountMapLocked(@NonNull StorageInfoProto storageInfoProto) {
-        mDocumentCountMapLocked.clear();
-        List<NamespaceStorageInfoProto> namespaceStorageInfoProtoList =
-                storageInfoProto.getDocumentStorageInfo().getNamespaceStorageInfoList();
-        for (int i = 0; i < namespaceStorageInfoProtoList.size(); i++) {
-            NamespaceStorageInfoProto namespaceStorageInfoProto =
-                    namespaceStorageInfoProtoList.get(i);
-            String packageName = getPackageName(namespaceStorageInfoProto.getNamespace());
-            Integer oldCount = mDocumentCountMapLocked.get(packageName);
-            int newCount;
-            if (oldCount == null) {
-                newCount = namespaceStorageInfoProto.getNumAliveDocuments();
-            } else {
-                newCount = oldCount + namespaceStorageInfoProto.getNumAliveDocuments();
-            }
-            mDocumentCountMapLocked.put(packageName, newCount);
-        }
-    }
-
     /** Wrapper around schema changes */
     @VisibleForTesting
     static class RewrittenSchemaResults {
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/DocumentLimiter.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/DocumentLimiter.java
new file mode 100644
index 0000000..5593461
--- /dev/null
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/DocumentLimiter.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appsearch.localstorage;
+
+import static androidx.appsearch.localstorage.util.PrefixUtil.getPackageName;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.appsearch.app.AppSearchResult;
+import androidx.appsearch.exceptions.AppSearchException;
+import androidx.appsearch.localstorage.util.MapUtil;
+import androidx.collection.ArrayMap;
+import androidx.core.util.Preconditions;
+
+import com.google.android.icing.proto.NamespaceStorageInfoProto;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A class that encapsulates per-package document count tracking and limit enforcement.
+ *
+ * This class is configured with a {@link #mDocumentLimitStartThreshold}. While the total number of
+ * documents in the system is below that threshold, all packages will be allowed to put as many
+ * documents into the index as they wish. Once the total number of documents exceed
+ * {@link #mDocumentLimitStartThreshold}, then each package will be limited to no more than
+ * {@link #mPerPackageDocumentCountLimit} documents.
+ *
+ *  <p>This class is not thread safe.
+ *
+ * @exportToFramework:hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class DocumentLimiter {
+    private final int mDocumentLimitStartThreshold;
+    private final int mPerPackageDocumentCountLimit;
+    private int mTotalDocumentCount;
+    private final Map<String, Integer> mDocumentCountMap;
+
+    /**
+     * @param documentLimitStartThreshold the total number of documents in the system at which the
+     *                                    limiter should begin applying the
+     *                                    perPackageDocumentCountLimit limit.
+     * @param perPackageDocumentCountLimit the maximum number of documents that each package is
+     *                                   allowed to have once the total number of documents in the
+     *                                   system exceeds documentLimitStartThreshold.
+     * @param namespaceStorageInfoProtoList a list of NamespaceStorageInfoProtos that reflects the
+     *                                     state of the index when this DocumentLimiter was created.
+     */
+    public DocumentLimiter(int documentLimitStartThreshold, int perPackageDocumentCountLimit,
+            @NonNull List<NamespaceStorageInfoProto> namespaceStorageInfoProtoList) {
+        mDocumentLimitStartThreshold = documentLimitStartThreshold;
+        mPerPackageDocumentCountLimit = perPackageDocumentCountLimit;
+        mTotalDocumentCount = 0;
+        mDocumentCountMap = new ArrayMap<>(namespaceStorageInfoProtoList.size());
+        buildDocumentCountMap(Preconditions.checkNotNull(namespaceStorageInfoProtoList));
+    }
+
+    /**
+     * Checks whether the package identified by packageName should be allowed to add another
+     * document.
+     *
+     * @param packageName the name of the package attempting to add the document
+     *
+     * @throws AppSearchException if the document limit is in force (because the total number of
+     * documents in the system exceeds {@link #mDocumentLimitStartThreshold}) and the package
+     * identified by packageName has already added more documents than
+     * {@link #mPerPackageDocumentCountLimit}.
+     */
+    public void enforceDocumentCountLimit(@NonNull String packageName) throws AppSearchException {
+        Preconditions.checkNotNull(packageName);
+        if (mTotalDocumentCount < mDocumentLimitStartThreshold) {
+            return;
+        }
+        Integer newDocumentCount = MapUtil.getOrDefault(mDocumentCountMap, packageName, 0) + 1;
+        if (newDocumentCount > mPerPackageDocumentCountLimit) {
+            // Now we really can't fit it in, even accounting for replacements.
+            throw new AppSearchException(
+                    AppSearchResult.RESULT_OUT_OF_SPACE,
+                    "Package \"" + packageName + "\" exceeded limit of "
+                            + mPerPackageDocumentCountLimit + " documents. Some documents "
+                            + "must be removed to index additional ones.");
+        }
+    }
+
+    /**
+     * Informs the DocumentLimiter that another document has been added for the package identified
+     * by package.
+     *
+     * @param packageName the name of the package that owns the added document.
+     */
+    public void reportDocumentAdded(@NonNull String packageName) {
+        Preconditions.checkNotNull(packageName);
+        ++mTotalDocumentCount;
+        Integer newDocumentCount = MapUtil.getOrDefault(mDocumentCountMap, packageName, 0) + 1;
+        mDocumentCountMap.put(packageName, newDocumentCount);
+    }
+
+    /**
+     * Informs the DocumentLimiter that numDocumentsDeleted documents, owned by the package
+     * identified by packageName, have been deleted.
+     *
+     * @param packageName the name of the package that owns the deleted documents.
+     * @param numDocumentsDeleted the number of documents that were deleted.
+     */
+    public void reportDocumentsRemoved(@NonNull String packageName, int numDocumentsDeleted) {
+        Preconditions.checkNotNull(packageName);
+        if (numDocumentsDeleted <= 0) {
+            return;
+        }
+        mTotalDocumentCount -= numDocumentsDeleted;
+        Integer oldDocumentCount = mDocumentCountMap.get(packageName);
+        // This should always be true: how can we delete documents for a package without
+        // having seen that package during init? This is just a safeguard.
+        if (oldDocumentCount != null) {
+            if (numDocumentsDeleted >= oldDocumentCount) {
+                mDocumentCountMap.remove(packageName);
+            } else {
+                mDocumentCountMap.put(packageName, oldDocumentCount - numDocumentsDeleted);
+            }
+        }
+    }
+
+    /**
+     * Informs the DocumentLimiter that the package identified by packageName has been removed from
+     * the system entirely.
+     *
+     * @param packageName the name of the package that was removed.
+     */
+    public void reportPackageRemoved(@NonNull String packageName) {
+        Preconditions.checkNotNull(packageName);
+        Integer oldDocumentCount = mDocumentCountMap.remove(packageName);
+        if (oldDocumentCount != null) {
+            // This should always be true: how can we remove a package without having seen that
+            // package during init? This is just a safeguard.
+            mTotalDocumentCount -= oldDocumentCount;
+        }
+    }
+
+    private void buildDocumentCountMap(
+            @NonNull List<NamespaceStorageInfoProto> namespaceStorageInfoProtoList) {
+        mDocumentCountMap.clear();
+        mTotalDocumentCount = 0;
+        for (int i = 0; i < namespaceStorageInfoProtoList.size(); i++) {
+            NamespaceStorageInfoProto namespaceStorageInfoProto =
+                    namespaceStorageInfoProtoList.get(i);
+            mTotalDocumentCount += namespaceStorageInfoProto.getNumAliveDocuments();
+            String packageName = getPackageName(namespaceStorageInfoProto.getNamespace());
+            Integer newCount =
+                    MapUtil.getOrDefault(mDocumentCountMap, packageName, 0)
+                            + namespaceStorageInfoProto.getNumAliveDocuments();
+            mDocumentCountMap.put(packageName, newCount);
+        }
+    }
+}
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LimitConfig.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LimitConfig.java
index ed74481..0844b3f 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LimitConfig.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LimitConfig.java
@@ -47,12 +47,19 @@
      * <p>This limit has two purposes:
      * <ol>
      *     <li>Protect icing lib's docid space from being overwhelmed by a single app. The
-     *     overall docid limit is currently 2^20 (~1 million)
+     *     overall docid limit is currently 2^22 (~4 million)
      *     <li>Prevent apps from using a very large amount of data on the system by storing too many
      *     documents.
      * </ol>
      */
-    int getMaxDocumentCount();
+    int getPerPackageDocumentCountLimit();
+
+    /**
+     * The number of total documents in the index at which AppSearch should start rationing docid
+     * space by limiting packages to add {@link #getPerPackageDocumentCountLimit()} documents to
+     * the index.
+     */
+    int getDocumentCountLimitStartThreshold();
 
     /**
      * The maximum number of suggestion results a single app is allowed to search.
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/UnlimitedLimitConfig.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/UnlimitedLimitConfig.java
index c27dbe7..eee5583 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/UnlimitedLimitConfig.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/UnlimitedLimitConfig.java
@@ -31,7 +31,12 @@
     }
 
     @Override
-    public int getMaxDocumentCount() {
+    public int getPerPackageDocumentCountLimit() {
+        return Integer.MAX_VALUE;
+    }
+
+    @Override
+    public int getDocumentCountLimitStartThreshold() {
         return Integer.MAX_VALUE;
     }
 
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/util/MapUtil.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/util/MapUtil.java
new file mode 100644
index 0000000..b77b89a
--- /dev/null
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/util/MapUtil.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appsearch.localstorage.util;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+import java.util.Map;
+
+/**
+ * A utility class to avoid the clutter of checking whether or not {@link Map#getOrDefault} is
+ * available.
+ *
+ * @exportToFramework:hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public final class MapUtil {
+    private MapUtil() {}
+
+    /**
+     * @return the value in map for key, or defaultValue if key is not present in map.
+     */
+    public static <K, V> V getOrDefault(@NonNull Map<K, V> map, K key, V defaultValue) {
+        V value = map.get(key);
+        return (value != null) ? value : defaultValue;
+    }
+}
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchEnvironment.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchEnvironment.java
index aa5326b..1b55264 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchEnvironment.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchEnvironment.java
@@ -19,11 +19,14 @@
 import android.content.Context;
 import android.os.UserHandle;
 
+import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
 
 import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.TimeUnit;
@@ -70,4 +73,28 @@
 
     /** Returns if we can log INFO level logs. */
     boolean isInfoLoggingEnabled();
+
+    /**
+     * The different environments that AppSearch code might be built in.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            JETPACK_ENVIRONMENT,
+            FRAMEWORK_ENVIRONMENT,
+            PLAY_SERVICES_ENVIRONMENT,
+    })
+    @interface EnvironmentType {
+    }
+
+    /** This code is being built in the Jetpack Environment */
+    static final int JETPACK_ENVIRONMENT = 1;
+
+    /** This code is being built in the Android Framework Environment */
+    static final int FRAMEWORK_ENVIRONMENT = 2;
+
+    /** This code is being built in the internal environment for Play Services code. */
+    static final int PLAY_SERVICES_ENVIRONMENT = 3;
+
+    /** Returns the {@code EnvironmentType} for this environment. */
+    @EnvironmentType int getEnvironment();
 }
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocument.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocument.java
index 28e9fa2..bb8440e 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocument.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/GenericDocument.java
@@ -184,6 +184,7 @@
     // GenericDocument is an open class that can be extended, whereas parcelable classes must be
     // final in those methods. Thus, we make this a system api to avoid 3p apps depending on it
     // and getting confused by the inheritability.
+    @SuppressWarnings("deprecation")
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
     @FlaggedApi(Flags.FLAG_ENABLE_GENERIC_DOCUMENT_OVER_IPC)
@@ -191,10 +192,31 @@
     @NonNull
     public static GenericDocument createFromParcel(@NonNull Parcel parcel) {
         Objects.requireNonNull(parcel);
-        GenericDocumentParcel documentParcel =
-                ParcelCompat.readParcelable(
-                        parcel, GenericDocumentParcel.class.getClassLoader(),
-                        GenericDocumentParcel.class);
+        GenericDocumentParcel documentParcel;
+        if (AppSearchEnvironmentFactory.getEnvironmentInstance().getEnvironment()
+                == AppSearchEnvironment.FRAMEWORK_ENVIRONMENT) {
+            // Code built in Framework cannot depend on Androidx libraries. Therefore, we must call
+            // Parcel#readParcelable directly.
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+                documentParcel =
+                        parcel.readParcelable(
+                                GenericDocumentParcel.class.getClassLoader(),
+                                GenericDocumentParcel.class);
+            } else {
+                // The Parcel#readParcelable(ClassLoader, Class) function has a known issue on
+                // Android T. This was fixed on Android U. When on Android T, call the older version
+                // of Parcel#readParcelable.
+                documentParcel =
+                        parcel.readParcelable(GenericDocumentParcel.class.getClassLoader());
+            }
+            // @exportToFramework:startStrip()
+        } else {
+            documentParcel =
+                    ParcelCompat.readParcelable(
+                            parcel, GenericDocumentParcel.class.getClassLoader(),
+                            GenericDocumentParcel.class);
+            // @exportToFramework:endStrip()
+        }
         return new GenericDocument(documentParcel);
     }
 
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/JetpackAppSearchEnvironment.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/JetpackAppSearchEnvironment.java
index bd13255..212ecfc 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/JetpackAppSearchEnvironment.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/JetpackAppSearchEnvironment.java
@@ -102,4 +102,10 @@
         // INFO logging is enabled by default in Jetpack AppSearch.
         return true;
     }
+
+    @Override
+    @EnvironmentType
+    public int getEnvironment() {
+        return AppSearchEnvironment.JETPACK_ENVIRONMENT;
+    }
 }
diff --git a/appsearch/exportToFramework.py b/appsearch/exportToFramework.py
index f898b04..373cd50 100755
--- a/appsearch/exportToFramework.py
+++ b/appsearch/exportToFramework.py
@@ -179,9 +179,6 @@
             .replace(
                     'androidx.core.util.ObjectsCompat',
                     'java.util.Objects')
-            .replace(
-                'import androidx.core.os.ParcelCompat',
-                'import android.os.Parcel')
             # Preconditions.checkNotNull is replaced with Objects.requireNonNull. We add both
             # imports and let google-java-format sort out which one is unused.
             .replace(
@@ -199,10 +196,6 @@
         contents = re.sub(r'\/\/ @exportToFramework:copyToPath\([^)]+\)', '', contents)
         contents = re.sub(r'@RequiresFeature\([^)]*\)', '', contents, flags=re.DOTALL)
 
-        contents = re.sub(
-                r'ParcelCompat\.readParcelable\(.*?([a-zA-Z.()]+),.*?([a-zA-Z.()]+),.*?([a-zA-Z.()]+)\)',
-                r'\1.readParcelable(\2)', contents, flags=re.DOTALL)
-
         # Jetpack methods have the Async suffix, but framework doesn't. Strip the Async suffix
         # to allow the same documentation to compile for both.
         contents = re.sub(r'(#[a-zA-Z0-9_]+)Async}', r'\1}', contents)
diff --git a/benchmark/integration-tests/macrobenchmark-target/lint-baseline.xml b/benchmark/integration-tests/macrobenchmark-target/lint-baseline.xml
index 0babca7..1d09ac4 100644
--- a/benchmark/integration-tests/macrobenchmark-target/lint-baseline.xml
+++ b/benchmark/integration-tests/macrobenchmark-target/lint-baseline.xml
@@ -1,5 +1,14 @@
 <?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.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
+
+    <issue
+        id="NewApi"
+        message="`&lt;androidx.benchmark.integration.macrobenchmark.target.AudioActivity>` requires API level 23 (current min is 21)"
+        errorLine1="        &lt;activity"
+        errorLine2="        ^">
+        <location
+            file="src/main/AndroidManifest.xml"/>
+    </issue>
 
     <issue
         id="BanThreadSleep"
diff --git a/biometric/biometric/src/main/res/values-kk/strings.xml b/biometric/biometric/src/main/res/values-kk/strings.xml
index 24ae7d4..42b808c2 100644
--- a/biometric/biometric/src/main/res/values-kk/strings.xml
+++ b/biometric/biometric/src/main/res/values-kk/strings.xml
@@ -17,7 +17,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fingerprint_dialog_touch_sensor" msgid="1072308044213194243">"Саусақ ізін оқу сканерін түртіңіз"</string>
+    <string name="fingerprint_dialog_touch_sensor" msgid="1072308044213194243">"Саусақ ізін оқу сканерін түртіңіз."</string>
     <string name="fingerprint_not_recognized" msgid="3873359464293253009">"Танылмады"</string>
     <string name="fingerprint_error_hw_not_available" msgid="8216738333501875566">"Саусақ ізі жабдығы қолжетімді емес."</string>
     <string name="fingerprint_error_no_fingerprints" msgid="7520712796891883488">"Саусақ іздері тіркелмеген."</string>
diff --git a/biometric/biometric/src/main/res/values-lt/strings.xml b/biometric/biometric/src/main/res/values-lt/strings.xml
index 180b3fe..8dc2c23 100644
--- a/biometric/biometric/src/main/res/values-lt/strings.xml
+++ b/biometric/biometric/src/main/res/values-lt/strings.xml
@@ -17,7 +17,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fingerprint_dialog_touch_sensor" msgid="1072308044213194243">"Palieskite piršto antspaudo jutiklį"</string>
+    <string name="fingerprint_dialog_touch_sensor" msgid="1072308044213194243">"Palieskite piršto atspaudo jutiklį"</string>
     <string name="fingerprint_not_recognized" msgid="3873359464293253009">"Neatpažinta"</string>
     <string name="fingerprint_error_hw_not_available" msgid="8216738333501875566">"Piršto antspaudo aparatinė įranga nepasiekiama."</string>
     <string name="fingerprint_error_no_fingerprints" msgid="7520712796891883488">"Neužregistruota jokių kontrolinių kodų."</string>
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index 245d61b..90a6d2b 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -99,7 +99,6 @@
 import org.gradle.build.event.BuildEventsListenerRegistry
 import org.gradle.jvm.tasks.Jar
 import org.gradle.kotlin.dsl.KotlinClosure1
-import org.gradle.kotlin.dsl.configure
 import org.gradle.kotlin.dsl.create
 import org.gradle.kotlin.dsl.dependencies
 import org.gradle.kotlin.dsl.extra
@@ -182,7 +181,7 @@
         project.tasks.withType(Zip::class.java).configureEach { it.configureForHermeticBuild() }
         project.tasks.withType(Copy::class.java).configureEach { it.configureForHermeticBuild() }
 
-        val allHostTests = project.tasks.register("allHostTests").get()
+        val allHostTests = project.tasks.register("allHostTests")
         // copy host side test results to DIST
         project.tasks.withType(AbstractTestTask::class.java) { task ->
             configureTestTask(project, task, allHostTests)
@@ -320,9 +319,9 @@
     private fun configureTestTask(
         project: Project,
         task: AbstractTestTask,
-        anchorTask: Task,
+        anchorTask: TaskProvider<Task>,
     ) {
-        if (task.name !in listOf("jvmStubsTest")) anchorTask.dependsOn(task)
+        anchorTask.configure { it.dependsOn(task) }
         val ignoreFailuresProperty =
             project.providers.gradleProperty(TEST_FAILURES_DO_NOT_FAIL_TEST_TASK)
         val ignoreFailures = ignoreFailuresProperty.isPresent
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
index 669021b..83cda4e 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
@@ -63,6 +63,7 @@
 import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockMismatchReport
 import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension
 import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
+import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
 
 /**
  * [AndroidXMultiplatformExtension] is an extension that wraps specific functionality of the Kotlin
@@ -681,8 +682,14 @@
         return if (project.enableWasmJs()) {
             kotlinExtension.wasmJs("wasmJs") {
                 block?.execute(this)
-                binaries.executable()
-                browser {}
+                binaries.library()
+                browser {
+                    testTask {
+                        it.useKarma { useChromeHeadless() }
+                        // TODO(b/367673246) Enable when ChromeHeadless is in AndroidX
+                        it.enabled = false
+                    }
+                }
                 project.configureWasm()
             }
         } else {
@@ -710,31 +717,22 @@
     }
 
     // Use DSL API when https://youtrack.jetbrains.com/issue/KT-70029 is closed for all tasks below
-    tasks.named("wasmJsDevelopmentExecutableCompileSync", DefaultIncrementalSyncTask::class.java) {
+    tasks.named("wasmJsDevelopmentLibraryCompileSync", DefaultIncrementalSyncTask::class.java) {
         it.destinationDirectory.set(
             file(layout.buildDirectory.dir("js/packages/wasm-js/dev/kotlin"))
         )
     }
-    tasks.named("wasmJsProductionExecutableCompileSync", DefaultIncrementalSyncTask::class.java) {
+    tasks.named("wasmJsProductionLibraryCompileSync", DefaultIncrementalSyncTask::class.java) {
         it.destinationDirectory.set(
             file(layout.buildDirectory.dir("js/packages/wasm-js/prod/kotlin"))
         )
     }
-    tasks.named(
-        "wasmJsTestTestDevelopmentExecutableCompileSync",
-        DefaultIncrementalSyncTask::class.java
-    ) {
-        it.destinationDirectory.set(
-            file(layout.buildDirectory.dir("js/packages/wasm-js-test/dev/kotlin"))
-        )
-    }
-    tasks.named(
-        "wasmJsTestTestProductionExecutableCompileSync",
-        DefaultIncrementalSyncTask::class.java
-    ) {
-        it.destinationDirectory.set(
-            file(layout.buildDirectory.dir("js/packages/wasm-js-test/prod/kotlin"))
-        )
+
+    // Compiler Arg needed for tests only: https://youtrack.jetbrains.com/issue/KT-59081
+    tasks.withType(Kotlin2JsCompile::class.java).configureEach { task ->
+        if (task.name.lowercase().contains("test")) {
+            task.compilerOptions.freeCompilerArgs.add("-Xwasm-enable-array-range-checks")
+        }
     }
 }
 
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/ListTaskOutputsTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/ListTaskOutputsTask.kt
index 016ea66..96b2c68 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/ListTaskOutputsTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/ListTaskOutputsTask.kt
@@ -142,6 +142,11 @@
         "wasmJsBrowserDevelopmentRun",
         "wasmJsBrowserProductionWebpack",
         "wasmJsBrowserProductionRun",
+
+        // Remove when https://youtrack.jetbrains.com/issue/KT-71688 is resolved and set
+        // destinationDirectory to the project's build directory
+        "wasmJsTestTestDevelopmentExecutableCompileSync",
+        "wasmJsTestTestProductionExecutableCompileSync",
     )
 
 fun shouldValidateTaskOutput(task: Task): Boolean {
diff --git a/camera/camera-camera2-pipe-integration/lint-baseline.xml b/camera/camera-camera2-pipe-integration/lint-baseline.xml
index fb33fef..75f90bd 100644
--- a/camera/camera-camera2-pipe-integration/lint-baseline.xml
+++ b/camera/camera-camera2-pipe-integration/lint-baseline.xml
@@ -1,284 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.7.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.7.0-alpha02)" variant="all" version="8.7.0-alpha02">
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 24, or core library desugaring (current min is 21): `java.lang.Iterable#forEach`"
-        errorLine1="                callbacks.forEach { callback -> addCaptureCallback(callback, executor) }"
-        errorLine2="                          ~~~~~~~">
-        <location
-            file="src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraCallbackMap.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 26 (current min is 21): `java.util.regex.Matcher#start` (called from `kotlin.text.MatchGroupCollection#get(String)`)"
-        errorLine1="        val quirkSettings = QuirkSettingsHolder.instance().get()"
-        errorLine2="                                                           ~~~">
-        <location
-            file="src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CameraQuirks.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="        assertThat(fakeRequestControl.torchUpdateEventList.removeFirstKt()).isTrue()"
-        errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="        assertThat(fakeRequestControl.torchUpdateEventList.removeFirstKt()).isFalse()"
-        errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="        assertThat(fakeRequestControl.torchUpdateEventList.removeFirstKt()).isTrue()"
-        errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="        assertThat(fakeRequestControl.torchUpdateEventList.removeFirstKt()).isFalse()"
-        errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="        assertThat(fakeRequestControl.torchUpdateEventList.removeFirstKt()).isTrue()"
-        errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="        assertThat(fakeRequestControl.torchUpdateEventList.removeFirstKt()).isFalse()"
-        errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 26 (current min is 21): `java.util.regex.Matcher#start` (called from `kotlin.text.MatchGroupCollection#get(String)`)"
-        errorLine1="                    .get(2, TimeUnit.SECONDS)"
-        errorLine2="                     ~~~">
-        <location
-            file="src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="        assertThat(fakeRequestControl.torchUpdateEventList.removeFirstKt() == state).isTrue()"
-        errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 26 (current min is 21): `java.util.regex.Matcher#start` (called from `kotlin.text.MatchGroupCollection#get(String)`)"
-        errorLine1="                        postviewDeferrableSurface.surface.get()!!"
-        errorLine2="                                                          ~~~">
-        <location
-            file="src/main/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManager.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 24, or core library desugaring (current min is 21): `java.lang.Iterable#forEach`"
-        errorLine1="        useCasesExpectedResultMap.keys.forEach {"
-        errorLine2="                                       ~~~~~~~">
-        <location
-            file="src/test/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombinationTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 24, or core library desugaring (current min is 21): `java.lang.Iterable#forEach`"
-        errorLine1="        useCasesExpectedDynamicRangeMap.keys.forEach {"
-        errorLine2="                                             ~~~~~~~">
-        <location
-            file="src/test/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombinationTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 24, or core library desugaring (current min is 21): `java.lang.Iterable#forEach`"
-        errorLine1="                useCases.forEach { put(it, it.currentConfig) }"
-        errorLine2="                         ~~~~~~~">
-        <location
-            file="src/test/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombinationTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 24, or core library desugaring (current min is 21): `java.lang.Iterable#forEach`"
-        errorLine1="                useCaseConfigs.forEach { put(it, supportedSizes.toList()) }"
-        errorLine2="                               ~~~~~~~">
-        <location
-            file="src/test/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombinationTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 24, or core library desugaring (current min is 21): `java.util.Map#forEach`"
-        errorLine1="        streamConfigMap.forEach { (streamConfig, deferrableSurface) ->"
-        errorLine2="                        ~~~~~~~">
-        <location
-            file="src/main/java/androidx/camera/camera2/pipe/integration/config/UseCaseCameraConfig.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 24, or core library desugaring (current min is 21): `java.lang.Iterable#forEach`"
-        errorLine1="        deferrableSurfaces.forEach {"
-        errorLine2="                           ~~~~~~~">
-        <location
-            file="src/main/java/androidx/camera/camera2/pipe/integration/config/UseCaseCameraConfig.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 24, or core library desugaring (current min is 21): `java.lang.Iterable#forEach`"
-        errorLine1="        forEach { captureConfig ->"
-        errorLine2="        ~~~~~~~">
-        <location
-            file="src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="        val lastRequest = fakeCameraGraph.fakeCameraGraphSession.repeatingRequests.removeLastKt()"
-        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControlTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="            fakeCameraGraph.fakeCameraGraphSession.repeatingRequests.removeLastKt()"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControlTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="        val lastRequest = fakeCameraGraph.fakeCameraGraphSession.repeatingRequests.removeLastKt()"
-        errorLine2="                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControlTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="                removeFirstKt()"
-        errorLine2="                ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="                removeFirstKt()"
-        errorLine2="                ~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 26 (current min is 21): `java.util.regex.Matcher#start` (called from `kotlin.text.MatchGroupCollection#get(String)`)"
-        errorLine1="                        cameraGraph.setSurface(it.id, deferrableSurface.surface.get())"
-        errorLine2="                                                                                ~~~">
-        <location
-            file="src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 24, or core library desugaring (current min is 21): `java.lang.Iterable#forEach`"
-        errorLine1="            [email protected] { useCase ->"
-        errorLine2="                                            ~~~~~~~">
-        <location
-            file="src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 24, or core library desugaring (current min is 21): `java.lang.Iterable#forEach`"
-        errorLine1="            [email protected] { useCase ->"
-        errorLine2="                                           ~~~~~~~">
-        <location
-            file="src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 24, or core library desugaring (current min is 21): `java.lang.Iterable#forEach`"
-        errorLine1="            forEach { useCase -> validatingBuilder.add(useCase.sessionConfig) }"
-        errorLine2="            ~~~~~~~">
-        <location
-            file="src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 24, or core library desugaring (current min is 21): `java.lang.Iterable#forEach`"
-        errorLine1="            forEach { useCase -> validatingBuilder.add(useCase.sessionConfig) }"
-        errorLine2="            ~~~~~~~">
-        <location
-            file="src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 24, or core library desugaring (current min is 21): `java.util.Map#forEach`"
-        errorLine1="                        surfaceToStreamMap.forEach {"
-        errorLine2="                                           ~~~~~~~">
-        <location
-            file="src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseSurfaceManager.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 26 (current min is 23): `java.util.regex.Matcher#start` (called from `kotlin.text.MatchGroupCollection#get(String)`)"
-        errorLine1="                        if (surface == testSessionParameters.deferrableSurface.surface.get()) {"
-        errorLine2="                                                                                       ~~~">
-        <location
-            file="src/androidTest/java/androidx/camera/camera2/pipe/integration/UseCaseSurfaceManagerDeviceTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level 26 (current min is 23): `java.util.regex.Matcher#start` (called from `kotlin.text.MatchGroupCollection#get(String)`)"
-        errorLine1="                        if (surface == testSessionParameters.deferrableSurface.surface.get()) {"
-        errorLine2="                                                                                       ~~~">
-        <location
-            file="src/androidTest/java/androidx/camera/camera2/pipe/integration/UseCaseSurfaceManagerDeviceTest.kt"/>
-    </issue>
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
 
     <issue
         id="CameraXQuirksClassDetector"
@@ -292,15 +13,6 @@
     <issue
         id="CameraXQuirksClassDetector"
         message="CameraX quirks should include this template in the javadoc:&#xA;&#xA;* &lt;p>QuirkSummary&#xA;*     Bug Id:&#xA;*     Description:&#xA;*     Device(s):"
-        errorLine1="public class ExcludedSupportedSizesQuirk : Quirk {"
-        errorLine2="             ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/ExcludedSupportedSizesQuirk.kt"/>
-    </issue>
-
-    <issue
-        id="CameraXQuirksClassDetector"
-        message="CameraX quirks should include this template in the javadoc:&#xA;&#xA;* &lt;p>QuirkSummary&#xA;*     Bug Id:&#xA;*     Description:&#xA;*     Device(s):"
         errorLine1="public class ExtraCroppingQuirk : Quirk {"
         errorLine2="             ~~~~~~~~~~~~~~~~~~">
         <location
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/ProcessingNodeDeviceTest.kt b/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/ProcessingNodeDeviceTest.kt
index 260965f..065ac04 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/ProcessingNodeDeviceTest.kt
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/ProcessingNodeDeviceTest.kt
@@ -34,6 +34,7 @@
 import androidx.camera.core.imagecapture.Utils.SENSOR_TO_BUFFER
 import androidx.camera.core.imagecapture.Utils.TIMESTAMP
 import androidx.camera.core.imagecapture.Utils.WIDTH
+import androidx.camera.core.imagecapture.Utils.createTakePictureRequest
 import androidx.camera.core.impl.Quirks
 import androidx.camera.core.impl.utils.Exif
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
@@ -144,7 +145,7 @@
     private suspend fun processYuvAndVerifyOutputSize(outputFileOptions: OutputFileOptions?) {
         // Arrange: create node with JPEG input and grayscale effect.
         val node = ProcessingNode(mainThreadExecutor(), null)
-        val nodeIn = ProcessingNode.In.of(ImageFormat.YUV_420_888, ImageFormat.JPEG)
+        val nodeIn = ProcessingNode.In.of(ImageFormat.YUV_420_888, listOf(ImageFormat.JPEG))
         val imageIn =
             createYuvFakeImageProxy(
                 CameraCaptureResultImageInfo(CAMERA_CAPTURE_RESULT),
@@ -167,7 +168,7 @@
                 null,
                 InternalImageProcessor(GrayscaleImageEffect())
             )
-        val nodeIn = ProcessingNode.In.of(ImageFormat.JPEG, ImageFormat.JPEG)
+        val nodeIn = ProcessingNode.In.of(ImageFormat.JPEG, listOf(ImageFormat.JPEG))
         val imageIn =
             createJpegFakeImageProxy(
                 CameraCaptureResultImageInfo(CAMERA_CAPTURE_RESULT),
@@ -194,11 +195,13 @@
         val processingRequest =
             ProcessingRequest(
                 { listOf() },
-                outputFileOptions,
-                CROP_RECT,
-                /*rotationDegrees=*/ 0, // 0 because exif does not have rotation.
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    if (outputFileOptions == null) null else listOf(outputFileOptions),
+                    CROP_RECT,
+                    SENSOR_TO_BUFFER,
+                    /*rotationDegrees=*/ 0, // 0 because exif does not have rotation.
+                    /*jpegQuality=*/ 100
+                ),
                 takePictureCallback,
                 Futures.immediateFuture(null)
             )
@@ -220,18 +223,20 @@
     ) {
         // Arrange: create a request with no cropping
         val node = ProcessingNode(mainThreadExecutor(), null)
-        val nodeIn = ProcessingNode.In.of(ImageFormat.JPEG, ImageFormat.JPEG)
+        val nodeIn = ProcessingNode.In.of(ImageFormat.JPEG, listOf(ImageFormat.JPEG))
         node.transform(nodeIn)
         val takePictureCallback = FakeTakePictureCallback()
 
         val processingRequest =
             ProcessingRequest(
                 { listOf() },
-                outputFileOptions,
-                Rect(0, 0, WIDTH, HEIGHT),
-                0,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    if (outputFileOptions == null) null else listOf(outputFileOptions),
+                    Rect(0, 0, WIDTH, HEIGHT),
+                    SENSOR_TO_BUFFER,
+                    /*rotationDegrees=*/ 0, // 0 because exif does not have rotation.
+                    /*jpegQuality=*/ 100
+                ),
                 takePictureCallback,
                 Futures.immediateFuture(null)
             )
@@ -260,18 +265,20 @@
         // Arrange: create a request with no cropping
         val format = ImageFormat.JPEG_R
         val node = ProcessingNode(mainThreadExecutor(), null)
-        val nodeIn = ProcessingNode.In.of(format, format)
+        val nodeIn = ProcessingNode.In.of(format, listOf(format))
         node.transform(nodeIn)
         val takePictureCallback = FakeTakePictureCallback()
 
         val processingRequest =
             ProcessingRequest(
                 { listOf() },
-                outputFileOptions,
-                Rect(0, 0, WIDTH, HEIGHT),
-                0,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    if (outputFileOptions == null) null else listOf(outputFileOptions),
+                    Rect(0, 0, WIDTH, HEIGHT),
+                    SENSOR_TO_BUFFER,
+                    /*rotationDegrees=*/ 0, // 0 because exif does not have rotation.
+                    /*jpegQuality=*/ 100
+                ),
                 takePictureCallback,
                 Futures.immediateFuture(null)
             )
@@ -306,18 +313,20 @@
     private suspend fun inMemoryInputPacket_callbackInvoked(outputFileOptions: OutputFileOptions?) {
         // Arrange.
         val node = ProcessingNode(mainThreadExecutor(), null)
-        val nodeIn = ProcessingNode.In.of(ImageFormat.JPEG, ImageFormat.JPEG)
+        val nodeIn = ProcessingNode.In.of(ImageFormat.JPEG, listOf(ImageFormat.JPEG))
         node.transform(nodeIn)
         val takePictureCallback = FakeTakePictureCallback()
 
         val processingRequest =
             ProcessingRequest(
                 { listOf() },
-                outputFileOptions,
-                Rect(0, 0, WIDTH, HEIGHT),
-                0,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    if (outputFileOptions == null) null else listOf(outputFileOptions),
+                    Rect(0, 0, WIDTH, HEIGHT),
+                    SENSOR_TO_BUFFER,
+                    /*rotationDegrees=*/ 0, // 0 because exif does not have rotation.
+                    /*jpegQuality=*/ 100
+                ),
                 takePictureCallback,
                 Futures.immediateFuture(null)
             )
@@ -346,18 +355,20 @@
         // Arrange.
         val format = ImageFormat.JPEG_R
         val node = ProcessingNode(mainThreadExecutor(), null)
-        val nodeIn = ProcessingNode.In.of(format, format)
+        val nodeIn = ProcessingNode.In.of(format, listOf(format))
         node.transform(nodeIn)
         val takePictureCallback = FakeTakePictureCallback()
 
         val processingRequest =
             ProcessingRequest(
                 { listOf() },
-                outputFileOptions,
-                Rect(0, 0, WIDTH, HEIGHT),
-                0,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    if (outputFileOptions == null) null else listOf(outputFileOptions),
+                    Rect(0, 0, WIDTH, HEIGHT),
+                    SENSOR_TO_BUFFER,
+                    /*rotationDegrees=*/ 0, // 0 because exif does not have rotation.
+                    /*jpegQuality=*/ 100
+                ),
                 takePictureCallback,
                 Futures.immediateFuture(null)
             )
@@ -385,7 +396,7 @@
     private suspend fun saveJpegOnDisk_verifyOutput(outputFileOptions: OutputFileOptions?) {
         // Arrange: create a on-disk processing request.
         val node = ProcessingNode(mainThreadExecutor(), null)
-        val nodeIn = ProcessingNode.In.of(ImageFormat.JPEG, ImageFormat.JPEG)
+        val nodeIn = ProcessingNode.In.of(ImageFormat.JPEG, listOf(ImageFormat.JPEG))
         node.transform(nodeIn)
         val takePictureCallback = FakeTakePictureCallback()
         val jpegBytes =
@@ -395,11 +406,13 @@
         val processingRequest =
             ProcessingRequest(
                 { listOf() },
-                outputFileOptions,
-                CROP_RECT,
-                0,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    if (outputFileOptions == null) null else listOf(outputFileOptions),
+                    CROP_RECT,
+                    SENSOR_TO_BUFFER,
+                    /*rotationDegrees=*/ 0, // 0 because exif does not have rotation.
+                    /*jpegQuality=*/ 100
+                ),
                 takePictureCallback,
                 Futures.immediateFuture(null)
             )
@@ -425,7 +438,7 @@
         // Arrange: create a on-disk processing request.
         val format = ImageFormat.JPEG_R
         val node = ProcessingNode(mainThreadExecutor(), null)
-        val nodeIn = ProcessingNode.In.of(format, format)
+        val nodeIn = ProcessingNode.In.of(format, listOf(format))
         node.transform(nodeIn)
         val takePictureCallback = FakeTakePictureCallback()
         val jpegBytes =
@@ -438,11 +451,13 @@
         val processingRequest =
             ProcessingRequest(
                 { listOf() },
-                outputFileOptions,
-                CROP_RECT,
-                0,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    if (outputFileOptions == null) null else listOf(outputFileOptions),
+                    CROP_RECT,
+                    SENSOR_TO_BUFFER,
+                    /*rotationDegrees=*/ 0, // 0 because exif does not have rotation.
+                    /*jpegQuality=*/ 100
+                ),
                 takePictureCallback,
                 Futures.immediateFuture(null)
             )
@@ -483,18 +498,20 @@
         // Force inject the quirk for the A24 incorrect JPEG metadata problem
         val node =
             ProcessingNode(mainThreadExecutor(), Quirks(listOf(IncorrectJpegMetadataQuirk())), null)
-        val nodeIn = ProcessingNode.In.of(ImageFormat.JPEG, ImageFormat.JPEG)
+        val nodeIn = ProcessingNode.In.of(ImageFormat.JPEG, listOf(ImageFormat.JPEG))
         node.transform(nodeIn)
         val takePictureCallback = FakeTakePictureCallback()
 
         val processingRequest =
             ProcessingRequest(
                 { listOf() },
-                null,
-                Rect(0, 0, WIDTH, HEIGHT),
-                0,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    /*outputFileOptions=*/ null,
+                    Rect(0, 0, WIDTH, HEIGHT),
+                    SENSOR_TO_BUFFER,
+                    /*rotationDegrees=*/ 0, // 0 because exif does not have rotation.
+                    /*jpegQuality=*/ 100
+                ),
                 takePictureCallback,
                 Futures.immediateFuture(null)
             )
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/Utils.kt b/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/Utils.kt
index a3672fd..7db6492 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/Utils.kt
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/imagecapture/Utils.kt
@@ -20,9 +20,11 @@
 import android.graphics.Rect
 import android.util.Size
 import androidx.camera.core.ImageCapture
+import androidx.camera.core.impl.utils.executor.CameraXExecutors
 import androidx.camera.testing.impl.fakes.FakeCameraCaptureResult
 import java.io.File
 import java.util.UUID
+import org.mockito.Mockito.mock
 
 object Utils {
     const val WIDTH = 640
@@ -46,4 +48,33 @@
             "hdrgm:Version=",
             "Item:Semantic=\"GainMap\"",
         )
+
+    fun createTakePictureRequest(
+        outputFileOptions: List<ImageCapture.OutputFileOptions>?,
+        cropRect: Rect,
+        sensorToBufferTransform: Matrix,
+        rotationDegrees: Int,
+        jpegQuality: Int
+    ): TakePictureRequest {
+        var onDiskCallback: ImageCapture.OnImageSavedCallback? = null
+        var onMemoryCallback: ImageCapture.OnImageCapturedCallback? = null
+        if (outputFileOptions == null) {
+            onMemoryCallback = mock(ImageCapture.OnImageCapturedCallback::class.java)
+        } else {
+            onDiskCallback = mock(ImageCapture.OnImageSavedCallback::class.java)
+        }
+
+        return TakePictureRequest.of(
+            CameraXExecutors.mainThreadExecutor(),
+            onMemoryCallback,
+            onDiskCallback,
+            outputFileOptions,
+            cropRect,
+            sensorToBufferTransform,
+            rotationDegrees,
+            jpegQuality,
+            ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY,
+            listOf()
+        )
+    }
 }
diff --git a/camera/camera-core/src/main/cpp/image_processing_util_jni.cc b/camera/camera-core/src/main/cpp/image_processing_util_jni.cc
index f49947a..b95be97 100644
--- a/camera/camera-core/src/main/cpp/image_processing_util_jni.cc
+++ b/camera/camera-core/src/main/cpp/image_processing_util_jni.cc
@@ -461,6 +461,7 @@
             height);
 
     if (result != 0) {
+        AndroidBitmap_unlockPixels(env,bitmap);
         return -1;
     }
 
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
index bd994cb..ef9a0f4 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
@@ -47,6 +47,7 @@
 import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_USE_SOFTWARE_JPEG_ENCODER;
 import static androidx.camera.core.impl.ImageInputConfig.OPTION_INPUT_DYNAMIC_RANGE;
 import static androidx.camera.core.impl.ImageInputConfig.OPTION_INPUT_FORMAT;
+import static androidx.camera.core.impl.ImageInputConfig.OPTION_SECONDARY_INPUT_FORMAT;
 import static androidx.camera.core.impl.ImageOutputConfig.OPTION_CUSTOM_ORDERED_RESOLUTIONS;
 import static androidx.camera.core.impl.ImageOutputConfig.OPTION_RESOLUTION_SELECTOR;
 import static androidx.camera.core.impl.UseCaseConfig.OPTION_CAPTURE_TYPE;
@@ -319,6 +320,13 @@
     public static final int OUTPUT_FORMAT_RAW = 2;
 
     /**
+     * Captures raw images in the {@link ImageFormat#RAW_SENSOR} and {@link ImageFormat#JPEG}
+     * image formats.
+     */
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    public static final int OUTPUT_FORMAT_RAW_JPEG = 3;
+
+    /**
      * Provides a static configuration with implementation-agnostic options.
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
@@ -479,6 +487,9 @@
         } else {
             if (isOutputFormatRaw(builder.getMutableConfig())) {
                 builder.getMutableConfig().insertOption(OPTION_INPUT_FORMAT, RAW_SENSOR);
+            } else if (isOutputFormatRawJpeg(builder.getMutableConfig())) {
+                builder.getMutableConfig().insertOption(OPTION_INPUT_FORMAT, RAW_SENSOR);
+                builder.getMutableConfig().insertOption(OPTION_SECONDARY_INPUT_FORMAT, JPEG);
             } else if (isOutputFormatUltraHdr(builder.getMutableConfig())) {
                 builder.getMutableConfig().insertOption(OPTION_INPUT_FORMAT, JPEG_R);
                 builder.getMutableConfig().insertOption(OPTION_INPUT_DYNAMIC_RANGE,
@@ -531,6 +542,11 @@
                 OUTPUT_FORMAT_RAW);
     }
 
+    private static boolean isOutputFormatRawJpeg(@NonNull MutableConfig config) {
+        return Objects.equals(config.retrieveOption(OPTION_OUTPUT_FORMAT, null),
+                OUTPUT_FORMAT_RAW_JPEG);
+    }
+
     /**
      * Configures flash mode to CameraControlInternal once it is ready.
      */
@@ -923,9 +939,31 @@
             final @NonNull OutputFileOptions outputFileOptions,
             final @NonNull Executor executor,
             final @NonNull OnImageSavedCallback imageSavedCallback) {
+        takePicture(List.of(outputFileOptions), executor, imageSavedCallback);
+    }
+
+    /**
+     * Captures two still images simultaneously and saves to a file along with application
+     * specified metadata.
+     *
+     * <p>Currently only {@link #OUTPUT_FORMAT_RAW_JPEG} is supporting simultaneous image capture.
+     *
+     * @param outputFileOptions  List of options to store the newly captured images.
+     * @param executor           The executor in which the callback methods will be run.
+     * @param imageSavedCallback Callback to be called for the newly captured image.
+     *
+     * @throws IllegalArgumentException If {@link ImageCapture#FLASH_MODE_SCREEN} is used without a
+     *                                  a non-null {@code ScreenFlash} instance set.
+     */
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    public void takePicture(
+            final @NonNull List<OutputFileOptions> outputFileOptions,
+            final @NonNull Executor executor,
+            final @NonNull OnImageSavedCallback imageSavedCallback) {
         if (Looper.getMainLooper() != Looper.myLooper()) {
             CameraXExecutors.mainThreadExecutor().execute(
-                    () -> takePicture(outputFileOptions, executor, imageSavedCallback));
+                    () -> takePicture(outputFileOptions,
+                            executor, imageSavedCallback));
             return;
         }
         takePictureInternal(executor, /*inMemoryCallback=*/null, imageSavedCallback,
@@ -1000,6 +1038,7 @@
 
             if (isRawSupported()) {
                 formats.add(OUTPUT_FORMAT_RAW);
+                formats.add(OUTPUT_FORMAT_RAW_JPEG);
             }
 
             return formats;
@@ -1407,7 +1446,7 @@
     private void takePictureInternal(@NonNull Executor executor,
             @Nullable OnImageCapturedCallback inMemoryCallback,
             @Nullable ImageCapture.OnImageSavedCallback onDiskCallback,
-            @Nullable OutputFileOptions outputFileOptions) {
+            @Nullable List<OutputFileOptions> outputFileOptions) {
         checkMainThread();
         if (getFlashMode() == ImageCapture.FLASH_MODE_SCREEN
                 && mScreenFlashWrapper.getBaseScreenFlash() == null) {
@@ -1644,7 +1683,8 @@
      */
     @OptIn(markerClass = androidx.camera.core.ExperimentalImageCaptureOutputFormat.class)
     @Target({ElementType.TYPE_USE})
-    @IntDef({OUTPUT_FORMAT_JPEG, OUTPUT_FORMAT_JPEG_ULTRA_HDR, OUTPUT_FORMAT_RAW})
+    @IntDef({OUTPUT_FORMAT_JPEG, OUTPUT_FORMAT_JPEG_ULTRA_HDR,
+            OUTPUT_FORMAT_RAW, OUTPUT_FORMAT_RAW_JPEG})
     @Retention(RetentionPolicy.SOURCE)
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public @interface OutputFormat {
@@ -2368,6 +2408,9 @@
             } else {
                 if (isOutputFormatRaw(getMutableConfig())) {
                     getMutableConfig().insertOption(OPTION_INPUT_FORMAT, RAW_SENSOR);
+                } else if (isOutputFormatRawJpeg(getMutableConfig())) {
+                    getMutableConfig().insertOption(OPTION_INPUT_FORMAT, RAW_SENSOR);
+                    getMutableConfig().insertOption(OPTION_SECONDARY_INPUT_FORMAT, JPEG);
                 } else if (isOutputFormatUltraHdr(getMutableConfig())) {
                     getMutableConfig().insertOption(OPTION_INPUT_FORMAT, JPEG_R);
                     getMutableConfig().insertOption(OPTION_INPUT_DYNAMIC_RANGE,
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/CaptureNode.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/CaptureNode.java
index c66c031..a987c2b 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/CaptureNode.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/CaptureNode.java
@@ -16,6 +16,9 @@
 
 package androidx.camera.core.imagecapture;
 
+import static android.graphics.ImageFormat.JPEG;
+import static android.graphics.ImageFormat.RAW_SENSOR;
+
 import static androidx.camera.core.ImageCapture.ERROR_CAPTURE_FAILED;
 import static androidx.camera.core.impl.utils.Threads.checkMainThread;
 import static androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor;
@@ -54,6 +57,8 @@
 
 import com.google.auto.value.AutoValue;
 
+import java.util.List;
+
 /**
  * A {@link Node} that calls back when all the images for one capture are received.
  *
@@ -79,6 +84,10 @@
     @Nullable
     SafeCloseImageReaderProxy mSafeCloseImageReaderProxy;
 
+    /* Additional image reader for simultaneous RAW + JPEG capture */
+    @Nullable
+    SafeCloseImageReaderProxy mSecondarySafeCloseImageReaderProxy;
+
     @Nullable
     SafeCloseImageReaderProxy mSafeCloseImageReaderForPostview;
 
@@ -88,6 +97,7 @@
     private In mInputEdge;
     @Nullable
     private NoMetadataImageReader mNoMetadataImageReader = null;
+
     @NonNull
     @Override
     public ProcessingNode.In transform(@NonNull In inputEdge) {
@@ -100,6 +110,7 @@
         // Create and configure ImageReader.
         Consumer<ProcessingRequest> requestConsumer;
         ImageReaderProxy wrappedImageReader;
+        ImageReaderProxy secondaryWrappedImageReader = null;
         boolean hasMetadata = !inputEdge.isVirtualCamera();
         CameraCaptureCallback progressCallback = new CameraCaptureCallback() {
             @Override
@@ -120,15 +131,36 @@
                 });
             }
         };
+
         CameraCaptureCallback cameraCaptureCallbacks;
+        CameraCaptureCallback secondaryCameraCaptureCallback = null;
+        boolean isSimultaneousCaptureEnabled = inputEdge.getOutputFormats().size() > 1;
         if (hasMetadata && inputEdge.getImageReaderProxyProvider() == null) {
-            // Use MetadataImageReader if the input edge expects metadata.
-            MetadataImageReader metadataImageReader = new MetadataImageReader(size.getWidth(),
-                    size.getHeight(), format, MAX_IMAGES);
-            cameraCaptureCallbacks =
-                    CameraCaptureCallbacks.createComboCallback(
-                            progressCallback, metadataImageReader.getCameraCaptureCallback());
-            wrappedImageReader = metadataImageReader;
+            if (isSimultaneousCaptureEnabled) {
+                MetadataImageReader metadataImageReader = new MetadataImageReader(size.getWidth(),
+                        size.getHeight(), JPEG, MAX_IMAGES);
+                cameraCaptureCallbacks =
+                        CameraCaptureCallbacks.createComboCallback(
+                                progressCallback, metadataImageReader.getCameraCaptureCallback());
+                wrappedImageReader = metadataImageReader;
+
+                MetadataImageReader secondaryMetadataImageReader = new MetadataImageReader(
+                        size.getWidth(), size.getHeight(), RAW_SENSOR, MAX_IMAGES);
+                secondaryCameraCaptureCallback =
+                        CameraCaptureCallbacks.createComboCallback(
+                                progressCallback,
+                                secondaryMetadataImageReader.getCameraCaptureCallback());
+                secondaryWrappedImageReader = secondaryMetadataImageReader;
+            } else {
+                // Use MetadataImageReader if the input edge expects metadata.
+                MetadataImageReader metadataImageReader = new MetadataImageReader(size.getWidth(),
+                        size.getHeight(), format, MAX_IMAGES);
+                cameraCaptureCallbacks =
+                        CameraCaptureCallbacks.createComboCallback(
+                                progressCallback, metadataImageReader.getCameraCaptureCallback());
+                wrappedImageReader = metadataImageReader;
+            }
+
             requestConsumer = this::onRequestAvailable;
         } else {
             cameraCaptureCallbacks = progressCallback;
@@ -144,33 +176,14 @@
             };
         }
         inputEdge.setCameraCaptureCallback(cameraCaptureCallbacks);
+        if (isSimultaneousCaptureEnabled && secondaryCameraCaptureCallback != null) {
+            inputEdge.setSecondaryCameraCaptureCallback(secondaryCameraCaptureCallback);
+        }
         inputEdge.setSurface(requireNonNull(wrappedImageReader.getSurface()));
         mSafeCloseImageReaderProxy = new SafeCloseImageReaderProxy(wrappedImageReader);
 
         // Listen to the input edges.
-        wrappedImageReader.setOnImageAvailableListener(imageReader -> {
-            try {
-                ImageProxy image = imageReader.acquireLatestImage();
-                if (image != null) {
-                    onImageProxyAvailable(image);
-                } else {
-                    if (mCurrentRequest != null) {
-                        sendCaptureError(
-                                TakePictureManager.CaptureError.of(mCurrentRequest.getRequestId(),
-                                        new ImageCaptureException(ERROR_CAPTURE_FAILED,
-                                                "Failed to acquire latest image", null)));
-                    }
-                }
-            } catch (IllegalStateException e) {
-                if (mCurrentRequest != null) {
-                    sendCaptureError(
-                            TakePictureManager.CaptureError.of(mCurrentRequest.getRequestId(),
-                                    new ImageCaptureException(
-                                            ERROR_CAPTURE_FAILED, "Failed to acquire latest image",
-                                            e)));
-                }
-            }
-        }, mainThreadExecutor());
+        setOnImageAvailableListener(wrappedImageReader);
 
         // Postview
         if (inputEdge.getPostviewSize() != null) {
@@ -196,10 +209,20 @@
                     inputEdge.getPostviewSize(), inputEdge.getPostviewImageFormat());
         }
 
+        // Simultaneous capture RAW + JPEG
+        if (isSimultaneousCaptureEnabled && secondaryWrappedImageReader != null) {
+            inputEdge.setSecondarySurface(secondaryWrappedImageReader.getSurface());
+            mSecondarySafeCloseImageReaderProxy = new SafeCloseImageReaderProxy(
+                    secondaryWrappedImageReader);
+            setOnImageAvailableListener(secondaryWrappedImageReader);
+        }
+
         inputEdge.getRequestEdge().setListener(requestConsumer);
         inputEdge.getErrorEdge().setListener(this::sendCaptureError);
 
-        mOutputEdge = ProcessingNode.In.of(inputEdge.getInputFormat(), inputEdge.getOutputFormat());
+        mOutputEdge = ProcessingNode.In.of(
+                inputEdge.getInputFormat(),
+                inputEdge.getOutputFormats());
 
         return mOutputEdge;
     }
@@ -215,6 +238,33 @@
         }
     }
 
+    private void setOnImageAvailableListener(@NonNull ImageReaderProxy imageReaderProxy) {
+        imageReaderProxy.setOnImageAvailableListener(imageReader -> {
+            try {
+                ImageProxy image = imageReader.acquireLatestImage();
+                if (image != null) {
+                    onImageProxyAvailable(image);
+                } else {
+                    if (mCurrentRequest != null) {
+                        sendCaptureError(
+                                TakePictureManager.CaptureError.of(
+                                        mCurrentRequest.getRequestId(),
+                                        new ImageCaptureException(ERROR_CAPTURE_FAILED,
+                                                "Failed to acquire latest image", null)));
+                    }
+                }
+            } catch (IllegalStateException e) {
+                if (mCurrentRequest != null) {
+                    sendCaptureError(
+                            TakePictureManager.CaptureError.of(mCurrentRequest.getRequestId(),
+                                    new ImageCaptureException(
+                                            ERROR_CAPTURE_FAILED,
+                                            "Failed to acquire latest image", e)));
+                }
+            }
+        }, mainThreadExecutor());
+    }
+
     @VisibleForTesting
     @MainThread
     void onImageProxyAvailable(@NonNull ImageProxy imageProxy) {
@@ -247,7 +297,20 @@
 
         // The capture is complete. Let the pipeline know it can take another picture.
         ProcessingRequest request = mCurrentRequest;
-        mCurrentRequest = null;
+
+        // If simultaneous capture RAW + JPEG, only reset when both images are processed.
+        boolean isSimultaneousCaptureEnabled = mInputEdge.getOutputFormats().size() > 1;
+        if (isSimultaneousCaptureEnabled && mCurrentRequest != null) {
+            mCurrentRequest.getTakePictureRequest()
+                    .markFormatProcessStatusInSimultaneousCapture(
+                            imageProxy.getFormat(), true);
+        }
+        boolean isProcessed = !isSimultaneousCaptureEnabled
+                || (isSimultaneousCaptureEnabled && mCurrentRequest.getTakePictureRequest()
+                .isFormatProcessedInSimultaneousCapture());
+        if (isProcessed) {
+            mCurrentRequest = null;
+        }
         request.onImageCaptured();
     }
 
@@ -309,12 +372,14 @@
         checkMainThread();
         releaseInputResources(requireNonNull(mInputEdge),
                 requireNonNull(mSafeCloseImageReaderProxy),
+                mSecondarySafeCloseImageReaderProxy,
                 mSafeCloseImageReaderForPostview);
 
     }
 
     private void releaseInputResources(@NonNull CaptureNode.In inputEdge,
             @NonNull SafeCloseImageReaderProxy imageReader,
+            @Nullable SafeCloseImageReaderProxy secondaryImageReader,
             @Nullable SafeCloseImageReaderProxy imageReaderForPostview) {
         inputEdge.getSurface().close();
         // Wait for the termination to close the ImageReader or the Surface may be released
@@ -331,6 +396,15 @@
                 }
             }, mainThreadExecutor());
         }
+
+        if (inputEdge.getOutputFormats().size() > 1 && inputEdge.getSecondarySurface() != null) {
+            inputEdge.getSecondarySurface().close();
+            inputEdge.getSecondarySurface().getTerminationFuture().addListener(() -> {
+                if (secondaryImageReader != null) {
+                    secondaryImageReader.safeClose();
+                }
+            }, mainThreadExecutor());
+        }
     }
 
     @VisibleForTesting
@@ -371,15 +445,24 @@
         private CameraCaptureCallback mCameraCaptureCallback = new CameraCaptureCallback() {
         };
 
+        /* Additional camera capture callback for simultaneous RAW + JPEG capture */
+        @Nullable
+        private CameraCaptureCallback mSecondaryCameraCaptureCallback;
+
         @Nullable
         private DeferrableSurface mSurface;
 
+        /* Additional surface for simultaneous RAW + JPEG capture */
+        @Nullable
+        private DeferrableSurface mSecondarySurface;
+
         @Nullable
         private DeferrableSurface mPostviewSurface = null;
 
         /**
          * Size of the {@link ImageReader} buffer.
          */
+        @NonNull
         abstract Size getSize();
 
         /**
@@ -390,10 +473,13 @@
         /**
          * The output format of the pipeline.
          *
-         * <p> For public users, only {@link ImageFormat#JPEG} and {@link ImageFormat#JPEG_R} are
-         * supported. Other formats are only used by in-memory capture in tests.
+         * <p> For public users, {@link ImageFormat#JPEG}, {@link ImageFormat#JPEG_R} and
+         * {@link ImageFormat#RAW_SENSOR}} are supported. Other formats are only used by in-memory
+         * capture in tests.
          */
-        abstract int getOutputFormat();
+        @SuppressWarnings("AutoValueImmutableFields")
+        @NonNull
+        abstract List<Integer> getOutputFormats();
 
         /**
          * Whether the pipeline is connected to a virtual camera.
@@ -449,6 +535,13 @@
             return mPostviewSurface;
         }
 
+        /**
+         * Edge that accepts the image frames for simultaneous RAW + JPEG capture.
+         */
+        @Nullable
+        DeferrableSurface getSecondarySurface() {
+            return mSecondarySurface;
+        }
 
         void setSurface(@NonNull Surface surface) {
             checkState(mSurface == null, "The surface is already set.");
@@ -459,6 +552,12 @@
             mPostviewSurface = new ImmediateSurface(surface, size, imageFormat);
         }
 
+        void setSecondarySurface(@NonNull Surface surface) {
+            checkState(mSecondarySurface == null, "The secondary surface is "
+                    + "already set.");
+            mSecondarySurface = new ImmediateSurface(surface, getSize(), getInputFormat());
+        }
+
         /**
          * Edge that accepts image metadata.
          *
@@ -473,19 +572,37 @@
             mCameraCaptureCallback = cameraCaptureCallback;
         }
 
+        @Nullable
+        CameraCaptureCallback getSecondaryCameraCaptureCallback() {
+            return mSecondaryCameraCaptureCallback;
+        }
+
+        void setSecondaryCameraCaptureCallback(
+                @NonNull CameraCaptureCallback cameraCaptureCallback) {
+            mSecondaryCameraCaptureCallback = cameraCaptureCallback;
+        }
+
         @NonNull
-        static In of(Size size, int inputFormat, int outputFormat, boolean isVirtualCamera,
+        static In of(
+                @NonNull Size size,
+                int inputFormat,
+                @NonNull List<Integer> outputFormats,
+                boolean isVirtualCamera,
                 @Nullable ImageReaderProxyProvider imageReaderProxyProvider) {
-            return new AutoValue_CaptureNode_In(size, inputFormat, outputFormat, isVirtualCamera,
+            return new AutoValue_CaptureNode_In(size, inputFormat, outputFormats, isVirtualCamera,
                     imageReaderProxyProvider, null, ImageFormat.YUV_420_888,
                     new Edge<>(), new Edge<>());
         }
 
         @NonNull
-        static In of(Size size, int inputFormat, int outputFormat, boolean isVirtualCamera,
+        static In of(
+                @NonNull Size size,
+                int inputFormat,
+                @NonNull List<Integer> outputFormats,
+                boolean isVirtualCamera,
                 @Nullable ImageReaderProxyProvider imageReaderProxyProvider,
                 @Nullable Size postviewSize, int postviewImageFormat) {
-            return new AutoValue_CaptureNode_In(size, inputFormat, outputFormat, isVirtualCamera,
+            return new AutoValue_CaptureNode_In(size, inputFormat, outputFormats, isVirtualCamera,
                     imageReaderProxyProvider, postviewSize, postviewImageFormat,
                     new Edge<>(), new Edge<>());
         }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ImagePipeline.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ImagePipeline.java
index 812be3d..124049a 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ImagePipeline.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ImagePipeline.java
@@ -126,11 +126,20 @@
                 cameraCharacteristics,
                 cameraEffect != null ? new InternalImageProcessor(cameraEffect) : null);
 
+        // Pass down [RAW_SENSOR, JPEG] to the pipeline if simultaneous capture is enabled.
+        List<Integer> outputFormats = new ArrayList<>();
+        if (mUseCaseConfig.getSecondaryInputFormat() != ImageFormat.UNKNOWN) {
+            outputFormats.add(ImageFormat.RAW_SENSOR);
+            outputFormats.add(ImageFormat.JPEG);
+        } else {
+            outputFormats.add(getOutputFormat());
+        }
+
         // Connect nodes
         mPipelineIn = CaptureNode.In.of(
                 cameraSurfaceSize,
                 mUseCaseConfig.getInputFormat(),
-                getOutputFormat(),
+                outputFormats,
                 isVirtualCamera,
                 mUseCaseConfig.getImageReaderProxyProvider(),
                 postviewSize,
@@ -147,6 +156,10 @@
         SessionConfig.Builder builder = SessionConfig.Builder.createFrom(mUseCaseConfig,
                 resolution);
         builder.addNonRepeatingSurface(mPipelineIn.getSurface());
+        if (mPipelineIn.getOutputFormats().size() > 1
+                && mPipelineIn.getSecondarySurface() != null) {
+            builder.addNonRepeatingSurface(mPipelineIn.getSecondarySurface());
+        }
 
         // Postview surface is generated when initializing CaptureNode.
         if (mPipelineIn.getPostviewSurface() != null) {
@@ -274,11 +287,7 @@
             @NonNull ListenableFuture<Void> captureFuture) {
         return new ProcessingRequest(
                 captureBundle,
-                takePictureRequest.getOutputFileOptions(),
-                takePictureRequest.getCropRect(),
-                takePictureRequest.getRotationDegrees(),
-                takePictureRequest.getJpegQuality(),
-                takePictureRequest.getSensorToBufferTransform(),
+                takePictureRequest,
                 takePictureCallback,
                 captureFuture,
                 requestId);
@@ -310,6 +319,10 @@
             builder.addAllCameraCaptureCallbacks(
                     takePictureRequest.getSessionConfigCameraCaptureCallbacks());
             builder.addSurface(mPipelineIn.getSurface());
+            if (mPipelineIn.getOutputFormats().size() > 1
+                    && mPipelineIn.getSecondarySurface() != null) {
+                builder.addSurface(mPipelineIn.getSecondarySurface());
+            }
             builder.setPostviewEnabled(shouldEnablePostview());
 
             // Sets the JPEG rotation and quality for JPEG and RAW formats. Some devices do not
@@ -332,6 +345,10 @@
             builder.addTag(tagBundleKey, captureStage.getId());
             builder.setId(requestId);
             builder.addCameraCaptureCallback(mPipelineIn.getCameraCaptureCallback());
+            if (mPipelineIn.getOutputFormats().size() > 1
+                    && mPipelineIn.getSecondaryCameraCaptureCallback() != null) {
+                builder.addCameraCaptureCallback(mPipelineIn.getSecondaryCameraCaptureCallback());
+            }
             captureConfigs.add(builder.build());
         }
 
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingNode.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingNode.java
index fddd9b7..1f1b7f4 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingNode.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingNode.java
@@ -54,6 +54,7 @@
 
 import com.google.auto.value.AutoValue;
 
+import java.util.List;
 import java.util.concurrent.Executor;
 
 /**
@@ -72,6 +73,10 @@
     @Nullable
     private final CameraCharacteristics mCameraCharacteristics;
 
+    @VisibleForTesting
+    @Nullable
+    DngImage2Disk mDngImage2Disk;
+
     private ProcessingNode.In mInputEdge;
     private Operation<InputPacket, Packet<ImageProxy>> mInput2Packet;
     private Operation<Image2JpegBytes.In, Packet<byte[]>> mImage2JpegBytes;
@@ -189,12 +194,25 @@
     void processInputPacket(@NonNull InputPacket inputPacket) {
         ProcessingRequest request = inputPacket.getProcessingRequest();
         try {
+            // If simultaneous capture RAW + JPEG, only trigger callback when both images
+            // are available and processed.
+            boolean isSimultaneousCaptureEnabled = mInputEdge.getOutputFormats().size() > 1;
             if (inputPacket.getProcessingRequest().isInMemoryCapture()) {
                 ImageProxy result = processInMemoryCapture(inputPacket);
-                mainThreadExecutor().execute(() -> request.onFinalResult(result));
+                boolean isProcessed = !isSimultaneousCaptureEnabled
+                        || (isSimultaneousCaptureEnabled && request.getTakePictureRequest()
+                        .isFormatProcessedInSimultaneousCapture());
+                if (isProcessed) {
+                    mainThreadExecutor().execute(() -> request.onFinalResult(result));
+                }
             } else {
                 ImageCapture.OutputFileResults result = processOnDiskCapture(inputPacket);
-                mainThreadExecutor().execute(() -> request.onFinalResult(result));
+                boolean isProcessed = !isSimultaneousCaptureEnabled
+                        || (isSimultaneousCaptureEnabled && request.getTakePictureRequest()
+                        .isFormatProcessedInSimultaneousCapture());
+                if (isProcessed) {
+                    mainThreadExecutor().execute(() -> request.onFinalResult(result));
+                }
             }
         } catch (ImageCaptureException e) {
             sendError(request, e);
@@ -209,7 +227,10 @@
 
     @WorkerThread
     void processPostviewInputPacket(@NonNull InputPacket inputPacket) {
-        int format = mInputEdge.getOutputFormat();
+        List<Integer> outputFormats = mInputEdge.getOutputFormats();
+        checkArgument(!outputFormats.isEmpty());
+
+        int format = outputFormats.get(0);
         checkArgument(format == YUV_420_888 || format == JPEG,
                 String.format("Postview only support YUV and JPEG output formats. "
                         + "Output format: %s", format));
@@ -228,37 +249,97 @@
     @WorkerThread
     ImageCapture.OutputFileResults processOnDiskCapture(@NonNull InputPacket inputPacket)
             throws ImageCaptureException {
-        int format = mInputEdge.getOutputFormat();
+        List<Integer> outputFormats = mInputEdge.getOutputFormats();
+        checkArgument(!outputFormats.isEmpty());
+        int format = outputFormats.get(0);
         checkArgument(isJpegFormats(format)
                 || isRawFormats(format),
                 String.format("On-disk capture only support JPEG and"
                 + " JPEG/R and RAW output formats. Output format: %s", format));
         ProcessingRequest request = inputPacket.getProcessingRequest();
+        checkArgument(request.getOutputFileOptions() != null
+                && !request.getOutputFileOptions().isEmpty(),
+                "OutputFileOptions cannot be empty");
         Packet<ImageProxy> originalImage = mInput2Packet.apply(inputPacket);
-
-        switch (format) {
-            case RAW_SENSOR:
-                DngImage2Disk dngImage2Disk = new DngImage2Disk(
-                        requireNonNull(mCameraCharacteristics),
-                        originalImage.getCameraCaptureResult().getCaptureResult());
-                return dngImage2Disk.apply(DngImage2Disk.In.of(
-                        originalImage.getData(),
-                        originalImage.getRotationDegrees(),
-                        requireNonNull(request.getOutputFileOptions())));
-            case JPEG:
-            default:
-                Packet<byte[]> jpegBytes = mImage2JpegBytes.apply(
-                        Image2JpegBytes.In.of(originalImage, request.getJpegQuality()));
-                if (jpegBytes.hasCropping() || mBitmapEffect != null) {
-                    jpegBytes = cropAndMaybeApplyEffect(jpegBytes, request.getJpegQuality());
-                }
-                return mJpegBytes2Disk.apply(
-                        JpegBytes2Disk.In.of(jpegBytes,
-                                requireNonNull(request.getOutputFileOptions())));
+        boolean isSimultaneousCaptureEnabled = outputFormats.size() > 1;
+        if (isSimultaneousCaptureEnabled) {
+            // If simultaneous capture RAW + JPEG, use the first output file options for JPEG and
+            // the second for RAW.
+            checkArgument(request.getOutputFileOptions() != null
+                            && request.getOutputFileOptions().size() > 1,
+                    "The number of OutputFileOptions for simultaneous capture "
+                            + "should be at least two");
+            ImageCapture.OutputFileResults outputFileResults = null;
+            switch (originalImage.getFormat()) {
+                case RAW_SENSOR:
+                    outputFileResults = saveRawToDisk(originalImage,
+                            requireNonNull(request.getOutputFileOptions()).get(0));
+                    request.getTakePictureRequest()
+                            .markFormatProcessStatusInSimultaneousCapture(RAW_SENSOR, true);
+                    return outputFileResults;
+                case JPEG:
+                default:
+                    outputFileResults = saveJpegToDisk(originalImage,
+                            requireNonNull(request.getOutputFileOptions()).get(1),
+                            request.getJpegQuality());
+                    request.getTakePictureRequest()
+                            .markFormatProcessStatusInSimultaneousCapture(JPEG, true);
+                    return outputFileResults;
+            }
+        } else {
+            switch (format) {
+                case RAW_SENSOR:
+                    return saveRawToDisk(originalImage,
+                            requireNonNull(request.getOutputFileOptions()).get(0));
+                case JPEG:
+                default:
+                    return saveJpegToDisk(originalImage,
+                            requireNonNull(request.getOutputFileOptions()).get(0),
+                            request.getJpegQuality());
+            }
         }
     }
 
     @NonNull
+    private ImageCapture.OutputFileResults saveRawToDisk(
+            @NonNull Packet<ImageProxy> originalImage,
+            @NonNull ImageCapture.OutputFileOptions outputFileOptions)
+            throws ImageCaptureException {
+
+        if (mDngImage2Disk == null) {
+            if (mCameraCharacteristics == null) {
+                throw new ImageCaptureException(ERROR_UNKNOWN,
+                        "CameraCharacteristics is null, DngCreator cannot be created", null);
+            }
+            if (originalImage.getCameraCaptureResult().getCaptureResult() == null) {
+                throw new ImageCaptureException(ERROR_UNKNOWN,
+                        "CameraCaptureResult is null, DngCreator cannot be created", null);
+            }
+            mDngImage2Disk = new DngImage2Disk(
+                    requireNonNull(mCameraCharacteristics),
+                    requireNonNull(originalImage.getCameraCaptureResult().getCaptureResult()));
+        }
+        return mDngImage2Disk.apply(DngImage2Disk.In.of(
+                originalImage.getData(),
+                originalImage.getRotationDegrees(),
+                requireNonNull(outputFileOptions)));
+    }
+
+    @NonNull
+    private ImageCapture.OutputFileResults saveJpegToDisk(
+            @NonNull Packet<ImageProxy> originalImage,
+            @NonNull ImageCapture.OutputFileOptions outputFileOptions,
+            int jpegQuality) throws ImageCaptureException {
+        Packet<byte[]> jpegBytes = mImage2JpegBytes.apply(
+                Image2JpegBytes.In.of(originalImage, jpegQuality));
+        if (jpegBytes.hasCropping() || mBitmapEffect != null) {
+            jpegBytes = cropAndMaybeApplyEffect(jpegBytes, jpegQuality);
+        }
+        return mJpegBytes2Disk.apply(
+                JpegBytes2Disk.In.of(jpegBytes, requireNonNull(outputFileOptions)));
+    }
+
+    @NonNull
     @WorkerThread
     ImageProxy processInMemoryCapture(@NonNull InputPacket inputPacket)
             throws ImageCaptureException {
@@ -266,9 +347,12 @@
         Packet<ImageProxy> image = mInput2Packet.apply(inputPacket);
         // TODO(b/322311893): Update to handle JPEG/R as output format in the if-statement when YUV
         //  to JPEG/R and effect with JPEG/R are supported.
+        List<Integer> outputFormats = mInputEdge.getOutputFormats();
+        checkArgument(!outputFormats.isEmpty());
+        int format = outputFormats.get(0);
+
         if ((image.getFormat() == YUV_420_888 || mBitmapEffect != null
-                || mHasIncorrectJpegMetadataQuirk)
-                && mInputEdge.getOutputFormat() == JPEG) {
+                || mHasIncorrectJpegMetadataQuirk) && (format == JPEG)) {
             Packet<byte[]> jpegBytes = mImage2JpegBytes.apply(
                     Image2JpegBytes.In.of(image, request.getJpegQuality()));
             if (mBitmapEffect != null) {
@@ -276,7 +360,13 @@
             }
             image = mJpegBytes2Image.apply(jpegBytes);
         }
-        return mJpegImage2Result.apply(image);
+        ImageProxy imageProxy = mJpegImage2Result.apply(image);
+        boolean isSimultaneousCaptureEnabled = outputFormats.size() > 1;
+        if (isSimultaneousCaptureEnabled) {
+            request.getTakePictureRequest()
+                    .markFormatProcessStatusInSimultaneousCapture(imageProxy.getFormat(), true);
+        }
+        return imageProxy;
     }
 
     /**
@@ -297,9 +387,11 @@
     /**
      * Sends {@link ImageCaptureException} to {@link TakePictureManager}.
      */
-    private static void sendError(@NonNull ProcessingRequest request,
+    private void sendError(@NonNull ProcessingRequest request,
             @NonNull ImageCaptureException e) {
-        mainThreadExecutor().execute(() -> request.onProcessFailure(e));
+        mainThreadExecutor().execute(() -> {
+            request.onProcessFailure(e);
+        });
     }
 
     /**
@@ -329,12 +421,14 @@
         /**
          * Get the main input edge that contains a {@link InputPacket} flow.
          */
+        @NonNull
         abstract Edge<InputPacket> getEdge();
 
 
         /**
          * Get the postview input edge.
          */
+        @NonNull
         abstract Edge<InputPacket> getPostviewEdge();
 
         /**
@@ -345,14 +439,18 @@
         /**
          * The output format of the pipeline.
          *
-         * <p> For public users, only {@link ImageFormat#JPEG} and {@link ImageFormat#JPEG_R} are
-         * supported. Other formats are only used by in-memory capture in tests.
+         * <p> For public users, {@link ImageFormat#JPEG}, {@link ImageFormat#JPEG_R} and
+         * {@link ImageFormat#RAW_SENSOR}} are supported. Other formats are only used by in-memory
+         * capture in tests.
          */
-        abstract int getOutputFormat();
+        @SuppressWarnings("AutoValueImmutableFields")
+        @NonNull
+        abstract List<Integer> getOutputFormats();
 
-        static In of(int inputFormat, int outputFormat) {
+        static In of(int inputFormat,
+                @NonNull List<Integer> outputFormats) {
             return new AutoValue_ProcessingNode_In(new Edge<>(), new Edge<>(),
-                    inputFormat, outputFormat);
+                    inputFormat, outputFormats);
         }
     }
 
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingRequest.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingRequest.java
index 35a7fc0..960ac92 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingRequest.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingRequest.java
@@ -41,8 +41,9 @@
  */
 class ProcessingRequest {
     private final int mRequestId;
+    @NonNull TakePictureRequest mTakePictureRequest;
     @Nullable
-    private final ImageCapture.OutputFileOptions mOutputFileOptions;
+    private final List<ImageCapture.OutputFileOptions> mOutputFileOptions;
     @NonNull
     private final Rect mCropRect;
     private final int mRotationDegrees;
@@ -62,32 +63,25 @@
 
     ProcessingRequest(
             @NonNull CaptureBundle captureBundle,
-            @Nullable ImageCapture.OutputFileOptions outputFileOptions,
-            @NonNull Rect cropRect,
-            int rotationDegrees,
-            int jpegQuality,
-            @NonNull Matrix sensorToBufferTransform,
+            @NonNull TakePictureRequest takePictureRequest,
             @NonNull TakePictureCallback callback,
             @NonNull ListenableFuture<Void> captureFuture) {
-        this(captureBundle, outputFileOptions, cropRect, rotationDegrees, jpegQuality,
-                sensorToBufferTransform, callback, captureFuture, 0);
+        this(captureBundle, takePictureRequest, callback, captureFuture, 0);
     }
     ProcessingRequest(
             @NonNull CaptureBundle captureBundle,
-            @Nullable ImageCapture.OutputFileOptions outputFileOptions,
-            @NonNull Rect cropRect,
-            int rotationDegrees,
-            int jpegQuality,
-            @NonNull Matrix sensorToBufferTransform,
+            @NonNull TakePictureRequest takePictureRequest,
             @NonNull TakePictureCallback callback,
             @NonNull ListenableFuture<Void> captureFuture,
             int requestId) {
         mRequestId = requestId;
-        mOutputFileOptions = outputFileOptions;
-        mJpegQuality = jpegQuality;
-        mRotationDegrees = rotationDegrees;
-        mCropRect = cropRect;
-        mSensorToBufferTransform = sensorToBufferTransform;
+        mTakePictureRequest = takePictureRequest;
+        mTakePictureRequest.initFormatProcessStatusInSimultaneousCapture();
+        mOutputFileOptions = takePictureRequest.getOutputFileOptions();
+        mJpegQuality = takePictureRequest.getJpegQuality();
+        mRotationDegrees = takePictureRequest.getRotationDegrees();
+        mCropRect = takePictureRequest.getCropRect();
+        mSensorToBufferTransform = takePictureRequest.getSensorToBufferTransform();
         mCallback = callback;
         mTagBundleKey = String.valueOf(captureBundle.hashCode());
         mStageIds = new ArrayList<>();
@@ -111,8 +105,13 @@
         return mRequestId;
     }
 
+    @NonNull
+    TakePictureRequest getTakePictureRequest() {
+        return mTakePictureRequest;
+    }
+
     @Nullable
-    ImageCapture.OutputFileOptions getOutputFileOptions() {
+    List<ImageCapture.OutputFileOptions> getOutputFileOptions() {
         return mOutputFileOptions;
     }
 
@@ -135,7 +134,7 @@
     }
 
     boolean isInMemoryCapture() {
-        return getOutputFileOptions() == null;
+        return getOutputFileOptions() == null || getOutputFileOptions().isEmpty();
     }
 
     /**
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/RequestWithCallback.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/RequestWithCallback.java
index 61dc018..4a401ab 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/RequestWithCallback.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/RequestWithCallback.java
@@ -49,6 +49,7 @@
     private final ListenableFuture<Void> mCompleteFuture;
     private CallbackToFutureAdapter.Completer<Void> mCaptureCompleter;
     private CallbackToFutureAdapter.Completer<Void> mCompleteCompleter;
+
     // Flag tracks if the request has been aborted by the UseCase. Once aborted, this class stops
     // propagating callbacks to the app.
     private boolean mIsAborted = false;
@@ -282,7 +283,11 @@
     }
 
     private void markComplete() {
-        checkState(!mCompleteFuture.isDone(), "The callback can only complete once.");
+        boolean isSimultaneousCapture = mTakePictureRequest.getOutputFileOptions() != null
+                && mTakePictureRequest.getOutputFileOptions().size() > 1;
+        if (!isSimultaneousCapture) {
+            checkState(!mCompleteFuture.isDone(), "The callback can only complete once.");
+        }
         mCompleteCompleter.set(null);
     }
 
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/TakePictureRequest.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/TakePictureRequest.java
index 4ef3f3b..8417457 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/TakePictureRequest.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/TakePictureRequest.java
@@ -16,6 +16,9 @@
 
 package androidx.camera.core.imagecapture;
 
+import static android.graphics.ImageFormat.JPEG;
+import static android.graphics.ImageFormat.RAW_SENSOR;
+
 import static androidx.camera.core.impl.utils.Threads.checkMainThread;
 import static androidx.core.util.Preconditions.checkArgument;
 
@@ -33,6 +36,7 @@
 import androidx.camera.core.ImageCapture;
 import androidx.camera.core.ImageCaptureException;
 import androidx.camera.core.ImageProxy;
+import androidx.camera.core.Logger;
 import androidx.camera.core.impl.CameraCaptureCallback;
 import androidx.camera.core.impl.ImageCaptureConfig;
 import androidx.camera.core.impl.ImageOutputConfig;
@@ -41,7 +45,9 @@
 
 import com.google.auto.value.AutoValue;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.Executor;
 
 /**
@@ -53,6 +59,8 @@
 @AutoValue
 public abstract class TakePictureRequest {
 
+    private static final String TAG = "TakePictureRequest";
+
     /**
      * By default, ImageCapture does not retry requests. For some problematic devices, the
      * capture request can become success after retrying. The allowed retry count will be
@@ -61,6 +69,17 @@
     private int mRemainingRetires = new CaptureFailedRetryEnabler().getRetryCount();
 
     /**
+     * Map to track image capture status for simultaneous capture.
+     *
+     * <p>For RAW + JPEG, we will only have two formats
+     * {@link android.graphics.ImageFormat#RAW_SENSOR} and {@link android.graphics.ImageFormat#JPEG}
+     * but it could extend to other formats in the future.
+     *
+     * It is not thread-safe.
+     */
+    private Map<Integer, Boolean> mFormatCaptureStatus = new HashMap<>();
+
+    /**
      * Gets the callback {@link Executor} provided by the app.
      */
     @NonNull
@@ -82,7 +101,7 @@
      * Gets the app provided options for on-disk capture.
      */
     @Nullable
-    abstract ImageCapture.OutputFileOptions getOutputFileOptions();
+    abstract List<ImageCapture.OutputFileOptions> getOutputFileOptions();
 
     /**
      * A snapshot of {@link ImageCapture#getViewPortCropRect()} when
@@ -166,6 +185,36 @@
         return mRemainingRetires;
     }
 
+    void initFormatProcessStatusInSimultaneousCapture() {
+        mFormatCaptureStatus.put(RAW_SENSOR, false);
+        mFormatCaptureStatus.put(JPEG, false);
+    }
+
+    /**
+     * Marks the format as processed in simultaneous capture.
+     */
+    void markFormatProcessStatusInSimultaneousCapture(int format, boolean isProcessed) {
+        if (!mFormatCaptureStatus.containsKey(format)) {
+            Logger.e(TAG, "The format is not supported in simultaneous capture");
+            return;
+        }
+        mFormatCaptureStatus.put(format, isProcessed);
+    }
+
+    /**
+     * Checks if all the formats are processed in simultaneous capture.
+     */
+    boolean isFormatProcessedInSimultaneousCapture() {
+        boolean isProcessed = true;
+        for (Map.Entry<Integer, Boolean> entry : mFormatCaptureStatus.entrySet()) {
+            if (!entry.getValue()) {
+                isProcessed = false;
+                break;
+            }
+        }
+        return isProcessed;
+    }
+
     /**
      * Delivers {@link ImageCaptureException} to the app.
      */
@@ -229,7 +278,7 @@
     public static TakePictureRequest of(@NonNull Executor appExecutor,
             @Nullable ImageCapture.OnImageCapturedCallback inMemoryCallback,
             @Nullable ImageCapture.OnImageSavedCallback onDiskCallback,
-            @Nullable ImageCapture.OutputFileOptions outputFileOptions,
+            @Nullable List<ImageCapture.OutputFileOptions> outputFileOptions,
             @NonNull Rect cropRect,
             @NonNull Matrix sensorToBufferTransform,
             int rotationDegrees,
@@ -241,8 +290,9 @@
         checkArgument((onDiskCallback == null) ^ (inMemoryCallback == null),
                 "One and only one on-disk or in-memory callback should be present.");
         return new AutoValue_TakePictureRequest(appExecutor, inMemoryCallback,
-                onDiskCallback, outputFileOptions, cropRect, sensorToBufferTransform,
-                rotationDegrees, jpegQuality, captureMode, sessionConfigCameraCaptureCallbacks);
+                onDiskCallback, outputFileOptions, cropRect,
+                sensorToBufferTransform, rotationDegrees, jpegQuality, captureMode,
+                sessionConfigCameraCaptureCallbacks);
     }
 
     /**
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/ImageInputConfig.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/ImageInputConfig.java
index f0c7c00..7e5ca65 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/ImageInputConfig.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/ImageInputConfig.java
@@ -16,6 +16,8 @@
 
 package androidx.camera.core.impl;
 
+import android.graphics.ImageFormat;
+
 import androidx.annotation.NonNull;
 import androidx.camera.core.Camera;
 import androidx.camera.core.DynamicRange;
@@ -25,6 +27,8 @@
 public interface ImageInputConfig extends ReadableConfig {
     Config.Option<Integer> OPTION_INPUT_FORMAT =
             Config.Option.create("camerax.core.imageInput.inputFormat", int.class);
+    Config.Option<Integer> OPTION_SECONDARY_INPUT_FORMAT =
+            Config.Option.create("camerax.core.imageInput.secondaryInputFormat", int.class);
     Config.Option<DynamicRange> OPTION_INPUT_DYNAMIC_RANGE =
             Config.Option.create("camerax.core.imageInput.inputDynamicRange",
                     DynamicRange.class);
@@ -48,6 +52,19 @@
     }
 
     /**
+     * Retrieve the secondary input image format.
+     *
+     * <p>This is the format that is required for simultaneous capture. Currently only RAW + JPEG
+     * are supported and the input format must be set to RAW and secondary input format must be set
+     * to JPEG.
+     *
+     * <p>If the secondary input format is not set, {@link ImageFormat#UNKNOWN} will be returned.
+     */
+    default int getSecondaryInputFormat() {
+        return retrieveOption(OPTION_SECONDARY_INPUT_FORMAT, ImageFormat.UNKNOWN);
+    }
+
+    /**
      * Retrieve the required input {@link DynamicRange}.
      *
      * <p>This is the dynamic range that is required as input and it must be
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/CaptureNodeTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/CaptureNodeTest.kt
index 816367e..80fac69 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/CaptureNodeTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/CaptureNodeTest.kt
@@ -17,6 +17,7 @@
 package androidx.camera.core.imagecapture
 
 import android.graphics.ImageFormat.JPEG
+import android.graphics.ImageFormat.RAW_SENSOR
 import android.graphics.ImageFormat.YUV_420_888
 import android.os.Build
 import android.os.Looper.getMainLooper
@@ -57,7 +58,7 @@
 
     @Before
     fun setUp() {
-        captureNodeIn = CaptureNode.In.of(Size(10, 10), JPEG, JPEG, false, null)
+        captureNodeIn = CaptureNode.In.of(Size(10, 10), JPEG, listOf(JPEG), false, null)
         captureNodeOut = captureNode.transform(captureNodeIn)
         captureNodeOut.edge.setListener { imagePropagated.add(it.imageProxy) }
     }
@@ -68,11 +69,47 @@
     }
 
     @Test
+    fun isNotSimultaneousCapture_createOneImageReaders() {
+        // Arrange: enable isSimultaneousCaptureEnabled in CaptureNode.In
+        val input = CaptureNode.In.of(Size(10, 10), RAW_SENSOR, listOf(RAW_SENSOR), false, null)
+
+        // Act: transform.
+        val node = CaptureNode()
+        val output = node.transform(input)
+
+        // Assert
+        assertThat(output.outputFormats.size).isEqualTo(1)
+        assertThat(output.outputFormats[0]).isEqualTo(RAW_SENSOR)
+        assertThat(input.surface).isNotNull()
+        assertThat(input.cameraCaptureCallback).isNotNull()
+        assertThat(input.secondarySurface).isNull()
+        assertThat(input.secondaryCameraCaptureCallback).isNull()
+    }
+
+    @Test
+    fun isSimultaneousCapture_createTwoImageReaders() {
+        // Arrange: enable isSimultaneousCaptureEnabled in CaptureNode.In
+        val input =
+            CaptureNode.In.of(Size(10, 10), RAW_SENSOR, listOf(RAW_SENSOR, JPEG), false, null)
+
+        // Act: transform.
+        val node = CaptureNode()
+        val output = node.transform(input)
+
+        // Assert
+        assertThat(output.outputFormats.size).isEqualTo(2)
+        assertThat(input.surface).isNotNull()
+        assertThat(input.cameraCaptureCallback).isNotNull()
+        assertThat(input.secondarySurface).isNotNull()
+        assertThat(input.secondaryCameraCaptureCallback).isNotNull()
+    }
+
+    @Test
     fun hasImageReaderProxyProvider_useTheProvidedImageReader() {
         // Arrange: create a fake ImageReaderProxyProvider.
         val imageReader = FakeImageReaderProxy(CaptureNode.MAX_IMAGES)
         val imageReaderProvider = ImageReaderProxyProvider { _, _, _, _, _ -> imageReader }
-        val input = CaptureNode.In.of(Size(10, 10), JPEG, JPEG, false, imageReaderProvider)
+        val input = CaptureNode.In.of(Size(10, 10), JPEG, listOf(JPEG), false, imageReaderProvider)
         // Act: transform.
         val node = CaptureNode()
         node.transform(input)
@@ -114,7 +151,7 @@
             CaptureNode.In.of(
                 Size(10, 10),
                 JPEG,
-                JPEG,
+                listOf(JPEG),
                 /* isVirtualCamera */ true,
                 { _, _, _, _, _ -> imageReaderProxy }
             )
@@ -175,7 +212,7 @@
             CaptureNode.In.of(
                 Size(10, 10),
                 JPEG,
-                JPEG,
+                listOf(JPEG),
                 /* isVirtualCamera */ true,
                 { _, _, _, _, _ -> imageReaderProxy }
             )
@@ -206,7 +243,7 @@
             CaptureNode.In.of(
                 Size(10, 10),
                 JPEG,
-                JPEG,
+                listOf(JPEG),
                 /* isVirtualCamera */ true,
                 { _, _, _, _, _ -> imageReaderProxy }
             )
@@ -241,7 +278,7 @@
             CaptureNode.In.of(
                 Size(10, 10),
                 JPEG,
-                JPEG,
+                listOf(JPEG),
                 /* isVirtualCamera */ true,
                 { _, _, _, _, _ -> imageReaderProxy }
             )
@@ -280,7 +317,15 @@
         val postviewSize = Size(640, 480)
 
         val input =
-            CaptureNode.In.of(Size(10, 10), JPEG, JPEG, false, null, postviewSize, YUV_420_888)
+            CaptureNode.In.of(
+                Size(10, 10),
+                JPEG,
+                listOf(JPEG),
+                false,
+                null,
+                postviewSize,
+                YUV_420_888
+            )
 
         // Act: transform.
         val node = CaptureNode()
@@ -298,7 +343,8 @@
         // Arrange: set the postviewSize to the CaptureNode.In
         val postviewSize = Size(640, 480)
 
-        val input = CaptureNode.In.of(Size(10, 10), JPEG, JPEG, false, null, postviewSize, JPEG)
+        val input =
+            CaptureNode.In.of(Size(10, 10), JPEG, listOf(JPEG), false, null, postviewSize, JPEG)
 
         // Act: transform.
         val node = CaptureNode()
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeProcessingRequest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeProcessingRequest.kt
index 658a6bb..7a40e02 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeProcessingRequest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeProcessingRequest.kt
@@ -20,6 +20,7 @@
 import android.graphics.Rect
 import androidx.camera.core.ImageCapture
 import androidx.camera.core.imagecapture.Utils.CROP_RECT
+import androidx.camera.core.imagecapture.Utils.createTakePictureRequest
 import androidx.camera.core.impl.CaptureBundle
 import androidx.concurrent.futures.CallbackToFutureAdapter
 import com.google.common.util.concurrent.ListenableFuture
@@ -27,6 +28,7 @@
 /** Fake [ProcessingRequest]. */
 internal class FakeProcessingRequest(
     outputFileOptions: ImageCapture.OutputFileOptions?,
+    secondaryOutputFileOptions: ImageCapture.OutputFileOptions?,
     captureBundle: CaptureBundle,
     cropRect: Rect,
     rotationDegrees: Int,
@@ -37,11 +39,15 @@
 ) :
     ProcessingRequest(
         captureBundle,
-        outputFileOptions,
-        cropRect,
-        rotationDegrees,
-        jpegQuality,
-        sensorToBufferTransform,
+        createTakePictureRequest(
+            if (outputFileOptions == null) null
+            else if (secondaryOutputFileOptions == null) listOf(outputFileOptions)
+            else listOf(outputFileOptions, secondaryOutputFileOptions),
+            cropRect,
+            sensorToBufferTransform,
+            rotationDegrees,
+            jpegQuality
+        ),
         callback,
         captureFuture
     ) {
@@ -49,5 +55,5 @@
         captureBundle: CaptureBundle,
         callback: TakePictureCallback,
         captureFuture: ListenableFuture<Void> = CallbackToFutureAdapter.getFuture { "test" }
-    ) : this(null, captureBundle, CROP_RECT, 0, 100, Matrix(), callback, captureFuture)
+    ) : this(null, null, captureBundle, CROP_RECT, 0, 100, Matrix(), callback, captureFuture)
 }
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeTakePictureRequest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeTakePictureRequest.kt
index 7b775e8..3ad213a 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeTakePictureRequest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeTakePictureRequest.kt
@@ -35,6 +35,7 @@
     private var imageCapturedCallback: OnImageCapturedCallback? = null
     private var imageSavedCallback: OnImageSavedCallback? = null
     private var fileOptions: ImageCapture.OutputFileOptions? = null
+    private var secondaryFileOptions: ImageCapture.OutputFileOptions? = null
     var exceptionReceived: ImageCaptureException? = null
     var imageReceived: ImageProxy? = null
     var fileReceived: ImageCapture.OutputFileResults? = null
@@ -108,8 +109,8 @@
         imageSavedCallback = onDiskCallback
     }
 
-    override fun getOutputFileOptions(): ImageCapture.OutputFileOptions? {
-        return fileOptions
+    override fun getOutputFileOptions(): List<ImageCapture.OutputFileOptions>? {
+        return listOfNotNull(fileOptions, secondaryFileOptions)
     }
 
     internal override fun getCropRect(): Rect {
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ImagePipelineTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ImagePipelineTest.kt
index 8ede6e5..7892120 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ImagePipelineTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ImagePipelineTest.kt
@@ -42,6 +42,7 @@
 import androidx.camera.core.imagecapture.Utils.JPEG_QUALITY
 import androidx.camera.core.imagecapture.Utils.OUTPUT_FILE_OPTIONS
 import androidx.camera.core.imagecapture.Utils.ROTATION_DEGREES
+import androidx.camera.core.imagecapture.Utils.SECONDARY_OUTPUT_FILE_OPTIONS
 import androidx.camera.core.imagecapture.Utils.SENSOR_TO_BUFFER
 import androidx.camera.core.imagecapture.Utils.SIZE
 import androidx.camera.core.imagecapture.Utils.WIDTH
@@ -66,6 +67,7 @@
 import androidx.camera.testing.impl.fakes.FakeImageReaderProxy
 import androidx.camera.testing.impl.fakes.GrayscaleImageEffect
 import androidx.core.util.Pair
+import com.google.common.collect.ImmutableList
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
@@ -198,7 +200,7 @@
     }
 
     @Test
-    fun createRequests_verifyCameraRequest_whenFormatIsRAw() {
+    fun createRequests_verifyCameraRequest_whenFormatIsRaw() {
         // Arrange.
         imageCaptureConfig = createImageCaptureConfig(inputFormat = ImageFormat.RAW_SENSOR)
         imagePipeline = ImagePipeline(imageCaptureConfig, SIZE, cameraCharacteristics)
@@ -213,6 +215,25 @@
     }
 
     @Test
+    fun createRequests_verifyCameraRequest_whenFormatIsRawAndSimultaneousCaptureEnabled() {
+        // Arrange.
+        imageCaptureConfig =
+            createImageCaptureConfig(
+                inputFormat = ImageFormat.RAW_SENSOR,
+                secondaryInputFormat = ImageFormat.JPEG
+            )
+        imagePipeline = ImagePipeline(imageCaptureConfig, SIZE, cameraCharacteristics)
+        val captureInput = imagePipeline.captureNode.inputEdge
+
+        // Act: create requests
+        val result =
+            imagePipeline.createRequests(IN_MEMORY_REQUEST, CALLBACK, Futures.immediateFuture(null))
+
+        // Assert: CameraRequest is constructed correctly.
+        verifyCaptureRequest(captureInput, result, true)
+    }
+
+    @Test
     fun createCameraRequestWithRotationQuirk_rotationNotInCaptureConfig() {
         // Arrange.
         injectRotationOptionQuirk()
@@ -352,7 +373,7 @@
 
                     override fun onError(exception: ImageCaptureException) {}
                 },
-                OUTPUT_FILE_OPTIONS,
+                ImmutableList.of(OUTPUT_FILE_OPTIONS, SECONDARY_OUTPUT_FILE_OPTIONS),
                 cropRect,
                 SENSOR_TO_BUFFER,
                 ROTATION_DEGREES,
@@ -439,6 +460,22 @@
         assertThat(CALLBACK.inMemoryResult!!.planes).isEqualTo(image.planes)
     }
 
+    @Test
+    fun sendInMemoryRequest_receivesImageProxy_whenSimultaneousCaptureEnabled() {
+        // Arrange & act.
+        imageCaptureConfig =
+            createImageCaptureConfig(
+                inputFormat = ImageFormat.RAW_SENSOR,
+                secondaryInputFormat = ImageFormat.JPEG
+            )
+        imagePipeline = ImagePipeline(imageCaptureConfig, SIZE, cameraCharacteristics)
+        val image = sendInMemoryRequest(imagePipeline)
+
+        // Assert: the image is received by TakePictureCallback.
+        assertThat(image.format).isEqualTo(ImageFormat.JPEG)
+        assertThat(CALLBACK.inMemoryResult).isNotNull()
+    }
+
     /** Creates a ImageProxy and sends it to the pipeline. */
     private fun sendInMemoryRequest(
         pipeline: ImagePipeline,
@@ -525,6 +562,7 @@
     private fun createImageCaptureConfig(
         templateType: Int = TEMPLATE_TYPE,
         inputFormat: Int = ImageFormat.JPEG,
+        secondaryInputFormat: Int? = null,
     ): ImageCaptureConfig {
         val builder =
             ImageCapture.Builder().setCaptureOptionUnpacker { _, builder ->
@@ -532,6 +570,12 @@
             }
         builder.mutableConfig.insertOption(OPTION_IO_EXECUTOR, mainThreadExecutor())
         builder.mutableConfig.insertOption(ImageInputConfig.OPTION_INPUT_FORMAT, inputFormat)
+        if (secondaryInputFormat != null) {
+            builder.mutableConfig.insertOption(
+                ImageInputConfig.OPTION_SECONDARY_INPUT_FORMAT,
+                secondaryInputFormat
+            )
+        }
         if (Build.VERSION.SDK_INT >= 34 && inputFormat == ImageFormat.JPEG_R) {
             builder.mutableConfig.insertOption(OPTION_INPUT_DYNAMIC_RANGE, HLG_10_BIT)
         }
@@ -541,13 +585,23 @@
 
     private fun verifyCaptureRequest(
         captureInput: CaptureNode.In,
-        result: Pair<CameraRequest, ProcessingRequest>
+        result: Pair<CameraRequest, ProcessingRequest>,
+        isSimultaneousCaptureEnabled: Boolean = false
     ) {
         val cameraRequest = result.first!!
         val captureConfig = cameraRequest.captureConfigs.single()
-        assertThat(captureConfig.cameraCaptureCallbacks)
-            .containsExactly(captureInput.cameraCaptureCallback)
-        assertThat(captureConfig.surfaces).containsExactly(captureInput.surface)
+        if (isSimultaneousCaptureEnabled) {
+            assertThat(captureConfig.cameraCaptureCallbacks)
+                .contains(captureInput.cameraCaptureCallback)
+            assertThat(captureConfig.cameraCaptureCallbacks)
+                .contains(captureInput.secondaryCameraCaptureCallback)
+            assertThat(captureConfig.surfaces).contains(captureInput.surface)
+            assertThat(captureConfig.surfaces).contains(captureInput.secondarySurface)
+        } else {
+            assertThat(captureConfig.cameraCaptureCallbacks)
+                .containsExactly(captureInput.cameraCaptureCallback)
+            assertThat(captureConfig.surfaces).containsExactly(captureInput.surface)
+        }
         assertThat(captureConfig.templateType).isEqualTo(TEMPLATE_TYPE)
         val jpegQuality = captureConfig.implementationOptions.retrieveOption(OPTION_JPEG_QUALITY)
         assertThat(jpegQuality).isEqualTo(JPEG_QUALITY)
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingInput2PacketTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingInput2PacketTest.kt
index 88695c5..d059a5a 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingInput2PacketTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingInput2PacketTest.kt
@@ -30,6 +30,7 @@
 import androidx.camera.core.imagecapture.Utils.SENSOR_TO_BUFFER
 import androidx.camera.core.imagecapture.Utils.WIDTH
 import androidx.camera.core.imagecapture.Utils.createProcessingRequest
+import androidx.camera.core.imagecapture.Utils.createTakePictureRequest
 import androidx.camera.core.imagecapture.Utils.injectRotationOptionQuirk
 import androidx.camera.core.impl.utils.futures.Futures
 import androidx.camera.core.internal.CameraCaptureResultImageInfo
@@ -137,11 +138,13 @@
         val processingRequest =
             ProcessingRequest(
                 { listOf() },
-                OUTPUT_FILE_OPTIONS,
-                CROP_RECT,
-                90,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    listOf(OUTPUT_FILE_OPTIONS),
+                    CROP_RECT,
+                    SENSOR_TO_BUFFER,
+                    /*rotationDegrees=*/ 90,
+                    /*jpegQuality=*/ 100
+                ),
                 FakeTakePictureCallback(),
                 Futures.immediateFuture(null)
             )
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingNodeTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingNodeTest.kt
index b6ab8c8..ba1e27a 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingNodeTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingNodeTest.kt
@@ -16,19 +16,23 @@
 
 package androidx.camera.core.imagecapture
 
-import android.graphics.ImageFormat
+import android.graphics.ImageFormat.JPEG
+import android.graphics.ImageFormat.RAW_SENSOR
 import android.graphics.Rect
 import android.hardware.camera2.CameraCharacteristics
 import android.os.Build
 import android.os.Looper.getMainLooper
+import androidx.camera.core.ImageCapture
 import androidx.camera.core.ImageCaptureException
 import androidx.camera.core.imagecapture.Utils.CAMERA_CAPTURE_RESULT
 import androidx.camera.core.imagecapture.Utils.HEIGHT
 import androidx.camera.core.imagecapture.Utils.OUTPUT_FILE_OPTIONS
 import androidx.camera.core.imagecapture.Utils.ROTATION_DEGREES
+import androidx.camera.core.imagecapture.Utils.SECONDARY_OUTPUT_FILE_OPTIONS
 import androidx.camera.core.imagecapture.Utils.SENSOR_TO_BUFFER
 import androidx.camera.core.imagecapture.Utils.WIDTH
 import androidx.camera.core.imagecapture.Utils.createProcessingRequest
+import androidx.camera.core.imagecapture.Utils.createTakePictureRequest
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.isSequentialExecutor
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
 import androidx.camera.core.impl.utils.futures.Futures
@@ -38,13 +42,16 @@
 import androidx.camera.testing.impl.TestImageUtil.createJpegFakeImageProxy
 import androidx.camera.testing.impl.TestImageUtil.createJpegrBytes
 import androidx.camera.testing.impl.TestImageUtil.createJpegrFakeImageProxy
+import androidx.camera.testing.impl.TestImageUtil.createRawFakeImageProxy
 import androidx.camera.testing.impl.fakes.FakeImageInfo
 import androidx.camera.testing.impl.fakes.FakeImageProxy
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito.any
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when`
 import org.robolectric.RobolectricTestRunner
 import org.robolectric.Shadows.shadowOf
 import org.robolectric.annotation.Config
@@ -65,7 +72,7 @@
 
     @Before
     fun setUp() {
-        processingNodeIn = ProcessingNode.In.of(ImageFormat.JPEG, ImageFormat.JPEG)
+        processingNodeIn = ProcessingNode.In.of(JPEG, listOf(JPEG))
         node.transform(processingNodeIn)
     }
 
@@ -76,11 +83,13 @@
         val request =
             ProcessingRequest(
                 { listOf() },
-                OUTPUT_FILE_OPTIONS,
-                Rect(0, 0, WIDTH, HEIGHT),
-                ROTATION_DEGREES,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    listOf(OUTPUT_FILE_OPTIONS),
+                    Rect(0, 0, WIDTH, HEIGHT),
+                    SENSOR_TO_BUFFER,
+                    ROTATION_DEGREES,
+                    /*jpegQuality=*/ 100
+                ),
                 callback,
                 Futures.immediateFuture(null)
             )
@@ -103,11 +112,13 @@
         val request =
             ProcessingRequest(
                 { listOf() },
-                OUTPUT_FILE_OPTIONS,
-                Rect(0, 0, WIDTH, HEIGHT),
-                ROTATION_DEGREES,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    listOf(OUTPUT_FILE_OPTIONS),
+                    Rect(0, 0, WIDTH, HEIGHT),
+                    SENSOR_TO_BUFFER,
+                    ROTATION_DEGREES,
+                    /*jpegQuality=*/ 100
+                ),
                 callback,
                 Futures.immediateFuture(null)
             )
@@ -123,6 +134,93 @@
     }
 
     @Test
+    fun processRequest_hasDiskResult_whenFormatIsRaw() {
+        // Arrange: create a request with callback.
+        processingNodeIn = ProcessingNode.In.of(RAW_SENSOR, listOf(RAW_SENSOR))
+        node.transform(processingNodeIn)
+
+        val callback = FakeTakePictureCallback()
+        val request =
+            ProcessingRequest(
+                { listOf() },
+                createTakePictureRequest(
+                    listOf(OUTPUT_FILE_OPTIONS),
+                    Rect(0, 0, WIDTH, HEIGHT),
+                    SENSOR_TO_BUFFER,
+                    ROTATION_DEGREES,
+                    /*jpegQuality=*/ 100
+                ),
+                callback,
+                Futures.immediateFuture(null)
+            )
+
+        // Act: process the request.
+        val rawImage =
+            createRawFakeImageProxy(
+                CameraCaptureResultImageInfo(CAMERA_CAPTURE_RESULT),
+                WIDTH,
+                HEIGHT
+            )
+        val dngImage2Disk = mock(DngImage2Disk::class.java)
+        node.mDngImage2Disk = dngImage2Disk
+        `when`(dngImage2Disk.apply(any(DngImage2Disk.In::class.java)))
+            .thenReturn(mock(ImageCapture.OutputFileResults::class.java))
+        processingNodeIn.edge.accept(ProcessingNode.InputPacket.of(request, rawImage))
+        shadowOf(getMainLooper()).idle()
+
+        // Assert: the image is saved.
+        assertThat(callback.onDiskResult).isNotNull()
+    }
+
+    @Test
+    fun processRequest_hasDiskResult_whenSimultaneousCaptureEnabled() {
+        // Arrange: create a request with callback.
+        processingNodeIn = ProcessingNode.In.of(RAW_SENSOR, listOf(RAW_SENSOR, JPEG))
+        node.transform(processingNodeIn)
+
+        val callback = FakeTakePictureCallback()
+        val request =
+            ProcessingRequest(
+                { listOf() },
+                createTakePictureRequest(
+                    listOf(OUTPUT_FILE_OPTIONS, SECONDARY_OUTPUT_FILE_OPTIONS),
+                    Rect(0, 0, WIDTH, HEIGHT),
+                    SENSOR_TO_BUFFER,
+                    ROTATION_DEGREES,
+                    /*jpegQuality=*/ 100
+                ),
+                callback,
+                Futures.immediateFuture(null)
+            )
+
+        // Act: process the request.
+        val jpegBytes = createJpegBytes(WIDTH, HEIGHT)
+        val jpegImage = createJpegFakeImageProxy(jpegBytes)
+        processingNodeIn.edge.accept(ProcessingNode.InputPacket.of(request, jpegImage))
+        shadowOf(getMainLooper()).idle()
+
+        // Assert: the image is saved.
+        assertThat(callback.onDiskResult).isNull()
+
+        // Act: process the request.
+        val rawImage =
+            createRawFakeImageProxy(
+                CameraCaptureResultImageInfo(CAMERA_CAPTURE_RESULT),
+                WIDTH,
+                HEIGHT
+            )
+        val dngImage2Disk = mock(DngImage2Disk::class.java)
+        node.mDngImage2Disk = dngImage2Disk
+        `when`(dngImage2Disk.apply(any(DngImage2Disk.In::class.java)))
+            .thenReturn(mock(ImageCapture.OutputFileResults::class.java))
+        processingNodeIn.edge.accept(ProcessingNode.InputPacket.of(request, rawImage))
+        shadowOf(getMainLooper()).idle()
+
+        // Assert: the image is saved.
+        assertThat(callback.onDiskResult).isNotNull()
+    }
+
+    @Test
     fun processAbortedRequest_noOps() {
         // Arrange: create a request with aborted callback.
         val callback = FakeTakePictureCallback()
@@ -130,11 +228,13 @@
         val request =
             ProcessingRequest(
                 { listOf() },
-                OUTPUT_FILE_OPTIONS,
-                Rect(0, 0, WIDTH, HEIGHT),
-                ROTATION_DEGREES,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    listOf(OUTPUT_FILE_OPTIONS),
+                    Rect(0, 0, WIDTH, HEIGHT),
+                    SENSOR_TO_BUFFER,
+                    ROTATION_DEGREES,
+                    /*jpegQuality=*/ 100
+                ),
                 callback,
                 Futures.immediateFuture(null)
             )
@@ -156,11 +256,13 @@
         val request =
             ProcessingRequest(
                 { listOf() },
-                OUTPUT_FILE_OPTIONS,
-                Rect(0, 0, WIDTH, HEIGHT),
-                ROTATION_DEGREES,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    listOf(OUTPUT_FILE_OPTIONS),
+                    Rect(0, 0, WIDTH, HEIGHT),
+                    SENSOR_TO_BUFFER,
+                    ROTATION_DEGREES,
+                    /*jpegQuality=*/ 100
+                ),
                 callback,
                 Futures.immediateFuture(null)
             )
@@ -183,11 +285,13 @@
         val request =
             ProcessingRequest(
                 { listOf() },
-                OUTPUT_FILE_OPTIONS,
-                Rect(0, 0, WIDTH, HEIGHT),
-                ROTATION_DEGREES,
-                /*jpegQuality=*/ 100,
-                SENSOR_TO_BUFFER,
+                createTakePictureRequest(
+                    listOf(OUTPUT_FILE_OPTIONS),
+                    Rect(0, 0, WIDTH, HEIGHT),
+                    SENSOR_TO_BUFFER,
+                    ROTATION_DEGREES,
+                    /*jpegQuality=*/ 100
+                ),
                 callback,
                 Futures.immediateFuture(null)
             )
@@ -241,7 +345,7 @@
         // Creates the ProcessingNode after updating the device name to load the correct quirks
         node = ProcessingNode(mainThreadExecutor(), cameraCharacteristics)
 
-        processingNodeIn = ProcessingNode.In.of(ImageFormat.JPEG, ImageFormat.JPEG)
+        processingNodeIn = ProcessingNode.In.of(JPEG, listOf(JPEG))
         node.transform(processingNodeIn)
 
         // Arrange: create an invalid ImageProxy.
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/Utils.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/Utils.kt
index 2a8bdd1..cd3061f 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/Utils.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/Utils.kt
@@ -30,6 +30,7 @@
 import androidx.camera.core.impl.ImageCaptureConfig
 import androidx.camera.core.impl.ImageInputConfig
 import androidx.camera.core.impl.TagBundle
+import androidx.camera.core.impl.utils.executor.CameraXExecutors
 import androidx.camera.core.impl.utils.futures.Futures
 import androidx.camera.core.internal.CameraCaptureResultImageInfo
 import androidx.camera.testing.impl.fakes.FakeCameraCaptureResult
@@ -37,6 +38,7 @@
 import androidx.camera.testing.impl.fakes.FakeImageProxy
 import java.io.File
 import java.util.UUID
+import org.mockito.Mockito.mock
 import org.robolectric.util.ReflectionHelpers.setStaticField
 
 /** Utility methods for testing image capture. */
@@ -59,6 +61,8 @@
             it.deleteOnExit()
         }
     internal val OUTPUT_FILE_OPTIONS = ImageCapture.OutputFileOptions.Builder(TEMP_FILE).build()
+    internal val SECONDARY_OUTPUT_FILE_OPTIONS =
+        ImageCapture.OutputFileOptions.Builder(TEMP_FILE).build()
     internal val CAMERA_CAPTURE_RESULT = FakeCameraCaptureResult().also { it.timestamp = TIMESTAMP }
 
     internal fun createProcessingRequest(
@@ -67,11 +71,13 @@
     ): ProcessingRequest {
         return ProcessingRequest(
             captureBundle,
-            OUTPUT_FILE_OPTIONS,
-            CROP_RECT,
-            ROTATION_DEGREES,
-            /*jpegQuality=*/ 100,
-            SENSOR_TO_BUFFER,
+            createTakePictureRequest(
+                listOf(OUTPUT_FILE_OPTIONS),
+                CROP_RECT,
+                SENSOR_TO_BUFFER,
+                ROTATION_DEGREES,
+                JPEG_QUALITY
+            ),
             takePictureCallback,
             Futures.immediateFuture(null)
         )
@@ -105,4 +111,33 @@
             }
         )
     }
+
+    fun createTakePictureRequest(
+        outputFileOptions: List<ImageCapture.OutputFileOptions>?,
+        cropRect: Rect,
+        sensorToBufferTransform: Matrix,
+        rotationDegrees: Int,
+        jpegQuality: Int
+    ): TakePictureRequest {
+        var onDiskCallback: ImageCapture.OnImageSavedCallback? = null
+        var onMemoryCallback: ImageCapture.OnImageCapturedCallback? = null
+        if (outputFileOptions == null) {
+            onMemoryCallback = mock(ImageCapture.OnImageCapturedCallback::class.java)
+        } else {
+            onDiskCallback = mock(ImageCapture.OnImageSavedCallback::class.java)
+        }
+
+        return TakePictureRequest.of(
+            CameraXExecutors.mainThreadExecutor(),
+            onMemoryCallback,
+            onDiskCallback,
+            outputFileOptions,
+            cropRect,
+            sensorToBufferTransform,
+            rotationDegrees,
+            jpegQuality,
+            ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY,
+            listOf()
+        )
+    }
 }
diff --git a/camera/camera-extensions/lint-baseline.xml b/camera/camera-extensions/lint-baseline.xml
index 5a613f6..6a92d51 100644
--- a/camera/camera-extensions/lint-baseline.xml
+++ b/camera/camera-extensions/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.7.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.7.0-alpha02)" variant="all" version="8.7.0-alpha02">
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
 
     <issue
         id="BanThreadSleep"
diff --git a/camera/camera-feature-combination-query-play-services/build.gradle b/camera/camera-feature-combination-query-play-services/build.gradle
index c971fd1..339be46 100644
--- a/camera/camera-feature-combination-query-play-services/build.gradle
+++ b/camera/camera-feature-combination-query-play-services/build.gradle
@@ -13,8 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import androidx.build.Publish
-import androidx.build.RunApiTasks
+
+import androidx.build.LibraryType
 
 plugins {
     id("AndroidXPlugin")
@@ -53,10 +53,9 @@
 }
 
 androidx {
-    name = "Jetpack Camera Feature Combination Play Services Library"
-    publish = Publish.NONE
+    name = "Camera Feature Combination Query With Play Services"
+    type = LibraryType.PUBLISHED_LIBRARY
     inceptionYear = "2024"
-    runApiTasks = new RunApiTasks.Yes()
-    description = "Camera feature combination components for the Jetpack Camera Library, a " +
+    description = "Camera feature combination components for the Camera Library, a " +
             "library providing camera feature combination with Google Play Services dependencies."
 }
diff --git a/camera/camera-feature-combination-query/build.gradle b/camera/camera-feature-combination-query/build.gradle
index 8276df3..525a8c8 100644
--- a/camera/camera-feature-combination-query/build.gradle
+++ b/camera/camera-feature-combination-query/build.gradle
@@ -13,8 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import androidx.build.Publish
-import androidx.build.RunApiTasks
+
+import androidx.build.LibraryType
 
 plugins {
     id("AndroidXPlugin")
@@ -47,10 +47,9 @@
 }
 
 androidx {
-    name = "Jetpack Camera Feature Combination Library"
-    publish = Publish.NONE
+    name = "Camera Feature Combination Query"
+    type = LibraryType.PUBLISHED_LIBRARY
     inceptionYear = "2024"
-    runApiTasks = new RunApiTasks.Yes()
-    description = "Camera feature combination components for the Jetpack Camera Library, a library " +
+    description = "Camera feature combination components for the Camera Library, a library " +
             "providing a seamless experience for querying camera features across all of Android."
 }
\ No newline at end of file
diff --git a/camera/camera-media3-effect/api/current.txt b/camera/camera-media3-effect/api/current.txt
index e6f50d0..11c61bc 100644
--- a/camera/camera-media3-effect/api/current.txt
+++ b/camera/camera-media3-effect/api/current.txt
@@ -1 +1,11 @@
 // Signature format: 4.0
+package androidx.camera.media3.effect {
+
+  public final class Media3Effect extends androidx.camera.core.CameraEffect implements java.lang.AutoCloseable {
+    ctor public Media3Effect(android.content.Context context, int targets, java.util.concurrent.Executor executor, androidx.core.util.Consumer<java.lang.Throwable> errorListener);
+    method public void close();
+    method public void setEffects(java.util.List<? extends androidx.media3.common.Effect> effects);
+  }
+
+}
+
diff --git a/camera/camera-media3-effect/api/restricted_current.txt b/camera/camera-media3-effect/api/restricted_current.txt
index e6f50d0..11c61bc 100644
--- a/camera/camera-media3-effect/api/restricted_current.txt
+++ b/camera/camera-media3-effect/api/restricted_current.txt
@@ -1 +1,11 @@
 // Signature format: 4.0
+package androidx.camera.media3.effect {
+
+  public final class Media3Effect extends androidx.camera.core.CameraEffect implements java.lang.AutoCloseable {
+    ctor public Media3Effect(android.content.Context context, int targets, java.util.concurrent.Executor executor, androidx.core.util.Consumer<java.lang.Throwable> errorListener);
+    method public void close();
+    method public void setEffects(java.util.List<? extends androidx.media3.common.Effect> effects);
+  }
+
+}
+
diff --git a/camera/camera-media3-effect/src/main/java/androidx/camera/media3/effect/Media3Effect.kt b/camera/camera-media3-effect/src/main/java/androidx/camera/media3/effect/Media3Effect.kt
index 3ea897d..2754e3d 100644
--- a/camera/camera-media3-effect/src/main/java/androidx/camera/media3/effect/Media3Effect.kt
+++ b/camera/camera-media3-effect/src/main/java/androidx/camera/media3/effect/Media3Effect.kt
@@ -18,7 +18,6 @@
 
 import android.annotation.SuppressLint
 import android.content.Context
-import androidx.annotation.RestrictTo
 import androidx.camera.core.CameraEffect
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
 import androidx.core.util.Consumer
@@ -32,6 +31,15 @@
  * allows the media3 [Effect] to be applied to the CameraX pipeline on the fly without restarting
  * the camera.
  *
+ * Code sample:
+ * ```
+ * media3Effect = Media3Effect(requireContext(), PREVIEW or VIDEO_CAPTURE, executor) {
+ *   Log.e(TAG, "Error in Media3Effect: $it")
+ * }
+ * media3Effect.setEffects(listOf(Brightness(1.5f)))
+ * cameraController.setEffects(setOf(media3Effect))
+ * ```
+ *
  * @param context the Android context
  * @param targets the target UseCase to which this effect should be applied. For details, see
  *   [CameraEffect].
@@ -40,7 +48,6 @@
  *   be the error thrown by this [Media3Effect]. This is invoked on the provided executor.
  */
 @SuppressLint("UnsafeOptInUsageError")
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public class Media3Effect(
     context: Context,
     targets: Int,
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
index aec2319..b8cdb7e 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
@@ -30,6 +30,7 @@
 import static androidx.camera.core.ImageCapture.OUTPUT_FORMAT_JPEG;
 import static androidx.camera.core.ImageCapture.OUTPUT_FORMAT_JPEG_ULTRA_HDR;
 import static androidx.camera.core.ImageCapture.OUTPUT_FORMAT_RAW;
+import static androidx.camera.core.ImageCapture.OUTPUT_FORMAT_RAW_JPEG;
 import static androidx.camera.core.ImageCapture.getImageCaptureCapabilities;
 import static androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY;
 import static androidx.camera.integration.core.CameraXViewModel.getConfiguredCameraXCameraImplementation;
@@ -975,6 +976,7 @@
         }
     }
 
+    @SuppressLint("RestrictedApiAndroidX")
     private void setUpTakePictureButton() {
         mTakePicture.setOnClickListener(
                 new View.OnClickListener() {
@@ -985,47 +987,64 @@
                         mImageSavedIdlingResource.increment();
                         mStartCaptureTime = SystemClock.elapsedRealtime();
 
-                        ImageCapture.OutputFileOptions outputFileOptions =
-                                createOutputFileOptions(mImageOutputFormat);
-                        getImageCapture().takePicture(outputFileOptions,
-                                mImageCaptureExecutorService,
-                                new ImageCapture.OnImageSavedCallback() {
-                                    @Override
-                                    public void onImageSaved(
-                                            @NonNull ImageCapture.OutputFileResults
-                                                    outputFileResults) {
-                                        Log.d(TAG, "Saved image to "
-                                                + outputFileResults.getSavedUri());
-                                        try {
-                                            mImageSavedIdlingResource.decrement();
-                                        } catch (IllegalStateException e) {
-                                            Log.e(TAG, "Error: unexpected onImageSaved "
-                                                    + "callback received. Continuing.");
-                                        }
+                        ImageCapture.OnImageSavedCallback callback = new ImageCapture
+                                .OnImageSavedCallback() {
+                            @Override
+                            public void onImageSaved(
+                                    @NonNull ImageCapture.OutputFileResults
+                                            outputFileResults) {
+                                Log.d(TAG, "Saved image to "
+                                        + outputFileResults.getSavedUri());
+                                try {
+                                    mImageSavedIdlingResource.decrement();
+                                } catch (IllegalStateException e) {
+                                    Log.e(TAG, "Error: unexpected onImageSaved "
+                                            + "callback received. Continuing.");
+                                }
 
-                                        long duration =
-                                                SystemClock.elapsedRealtime() - mStartCaptureTime;
-                                        runOnUiThread(() -> Toast.makeText(CameraXActivity.this,
-                                                "Image captured in " + duration + " ms",
-                                                Toast.LENGTH_SHORT).show());
-                                        if (mSessionImagesUriSet != null) {
-                                            mSessionImagesUriSet.add(
-                                                    requireNonNull(
-                                                            outputFileResults.getSavedUri()));
-                                        }
-                                    }
+                                long duration =
+                                        SystemClock.elapsedRealtime()
+                                                - mStartCaptureTime;
+                                runOnUiThread(() -> Toast.makeText(CameraXActivity.this,
+                                        "Image captured in " + duration + " ms",
+                                        Toast.LENGTH_SHORT).show());
+                                if (mSessionImagesUriSet != null) {
+                                    mSessionImagesUriSet.add(
+                                            requireNonNull(
+                                                    outputFileResults.getSavedUri()));
+                                }
+                            }
 
-                                    @Override
-                                    public void onError(@NonNull ImageCaptureException exception) {
-                                        Log.e(TAG, "Failed to save image.", exception);
+                            @Override
+                            public void onError(
+                                    @NonNull ImageCaptureException exception) {
+                                Log.e(TAG, "Failed to save image.", exception);
 
-                                        mLastTakePictureErrorMessage =
-                                                getImageCaptureErrorMessage(exception);
-                                        if (!mImageSavedIdlingResource.isIdleNow()) {
-                                            mImageSavedIdlingResource.decrement();
-                                        }
-                                    }
-                                });
+                                mLastTakePictureErrorMessage =
+                                        getImageCaptureErrorMessage(exception);
+                                if (!mImageSavedIdlingResource.isIdleNow()) {
+                                    mImageSavedIdlingResource.decrement();
+                                }
+                            }
+                        };
+
+                        if (mImageOutputFormat == OUTPUT_FORMAT_RAW_JPEG) {
+                            ImageCapture.OutputFileOptions rawOutputFileOptions =
+                                    createOutputFileOptions(OUTPUT_FORMAT_RAW);
+                            ImageCapture.OutputFileOptions jpegOutputFileOptions =
+                                    createOutputFileOptions(OUTPUT_FORMAT_JPEG);
+                            getImageCapture().takePicture(
+                                    List.of(rawOutputFileOptions, jpegOutputFileOptions),
+                                    mImageCaptureExecutorService,
+                                    callback);
+                        } else {
+                            ImageCapture.OutputFileOptions outputFileOptions =
+                                    createOutputFileOptions(mImageOutputFormat);
+                            getImageCapture().takePicture(
+                                    outputFileOptions,
+                                    mImageCaptureExecutorService,
+                                    callback);
+                        }
                     }
                 });
     }
@@ -1058,9 +1077,9 @@
         contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
         contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimetype);
         return new ImageCapture.OutputFileOptions.Builder(
-                        getContentResolver(),
-                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
-                        contentValues).build();
+                getContentResolver(),
+                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+                contentValues).build();
     }
 
 
@@ -2654,6 +2673,8 @@
             return "Ultra HDR";
         } else if (format == OUTPUT_FORMAT_RAW) {
             return "Raw";
+        } else if (format == OUTPUT_FORMAT_RAW_JPEG) {
+            return "Raw + Jpeg";
         }
         return "?";
     }
@@ -2667,6 +2688,8 @@
             return "Ultra HDR";
         } else if (format == OUTPUT_FORMAT_RAW) {
             return "Raw";
+        } else if (format == OUTPUT_FORMAT_RAW_JPEG) {
+            return "Raw + Jpeg";
         }
         return "Unknown format";
     }
@@ -2679,6 +2702,8 @@
             return 1;
         } else if (format == OUTPUT_FORMAT_RAW) {
             return 2;
+        } else if (format == OUTPUT_FORMAT_RAW_JPEG) {
+            return 3;
         } else {
             throw new IllegalArgumentException("Undefined output format: " + format);
         }
@@ -2694,6 +2719,8 @@
                 return OUTPUT_FORMAT_JPEG_ULTRA_HDR;
             case 2:
                 return OUTPUT_FORMAT_RAW;
+            case 3:
+                return OUTPUT_FORMAT_RAW_JPEG;
             default:
                 throw new IllegalArgumentException("Undefined item id: " + itemId);
         }
@@ -2728,7 +2755,7 @@
     @OptIn(markerClass = ExperimentalCamera2Interop.class)
     private static int getCamera2LensFacing(@NonNull CameraInfo cameraInfo) {
         Integer lensFacing = Camera2CameraInfo.from(cameraInfo).getCameraCharacteristic(
-                    CameraCharacteristics.LENS_FACING);
+                CameraCharacteristics.LENS_FACING);
 
         return lensFacing == null ? CameraCharacteristics.LENS_FACING_BACK : lensFacing;
     }
diff --git a/camera/integration-tests/extensionstestapp/lint-baseline.xml b/camera/integration-tests/extensionstestapp/lint-baseline.xml
index 5899a3e..18dc08f 100644
--- a/camera/integration-tests/extensionstestapp/lint-baseline.xml
+++ b/camera/integration-tests/extensionstestapp/lint-baseline.xml
@@ -1,5 +1,14 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.6.0-beta01" type="baseline" client="gradle" dependencies="false" name="AGP (8.6.0-beta01)" variant="all" version="8.6.0-beta01">
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
+
+    <issue
+        id="NewApi"
+        message="`&lt;androidx.camera.integration.extensions.Camera2ExtensionsActivity>` requires API level 31 (current min is 21)"
+        errorLine1="        &lt;activity"
+        errorLine2="        ^">
+        <location
+            file="src/main/AndroidManifest.xml"/>
+    </issue>
 
     <issue
         id="RestrictedApiAndroidX"
diff --git a/car/app/app/lint-baseline.xml b/car/app/app/lint-baseline.xml
index cc73de5..53ef909 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.7.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.7.0-alpha02)" variant="all" version="8.7.0-alpha02">
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
 
     <issue
         id="MissingPermission"
diff --git a/collection/collection/api/current.txt b/collection/collection/api/current.txt
index d7df27b..5abb019 100644
--- a/collection/collection/api/current.txt
+++ b/collection/collection/api/current.txt
@@ -148,6 +148,8 @@
   }
 
   public final class DoubleListKt {
+    method public static inline androidx.collection.DoubleList buildDoubleList(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableDoubleList,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.DoubleList buildDoubleList(kotlin.jvm.functions.Function1<? super androidx.collection.MutableDoubleList,kotlin.Unit> builderAction);
     method public static androidx.collection.DoubleList doubleListOf();
     method public static androidx.collection.DoubleList doubleListOf(double element1);
     method public static androidx.collection.DoubleList doubleListOf(double element1, double element2);
@@ -198,6 +200,8 @@
   }
 
   public final class FloatFloatMapKt {
+    method public static inline androidx.collection.FloatFloatMap buildFloatFloatMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatFloatMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.FloatFloatMap buildFloatFloatMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatFloatMap,kotlin.Unit> builderAction);
     method public static androidx.collection.FloatFloatMap emptyFloatFloatMap();
     method public static androidx.collection.FloatFloatMap floatFloatMapOf();
     method public static androidx.collection.FloatFloatMap floatFloatMapOf(float key1, float value1);
@@ -261,6 +265,8 @@
   }
 
   public final class FloatIntMapKt {
+    method public static inline androidx.collection.FloatIntMap buildFloatIntMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatIntMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.FloatIntMap buildFloatIntMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatIntMap,kotlin.Unit> builderAction);
     method public static androidx.collection.FloatIntMap emptyFloatIntMap();
     method public static androidx.collection.FloatIntMap floatIntMapOf();
     method public static androidx.collection.FloatIntMap floatIntMapOf(float key1, int value1);
@@ -330,6 +336,8 @@
   }
 
   public final class FloatListKt {
+    method public static inline androidx.collection.FloatList buildFloatList(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatList,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.FloatList buildFloatList(kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatList,kotlin.Unit> builderAction);
     method public static androidx.collection.FloatList emptyFloatList();
     method public static androidx.collection.FloatList floatListOf();
     method public static androidx.collection.FloatList floatListOf(float element1);
@@ -380,6 +388,8 @@
   }
 
   public final class FloatLongMapKt {
+    method public static inline androidx.collection.FloatLongMap buildFloatLongMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatLongMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.FloatLongMap buildFloatLongMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatLongMap,kotlin.Unit> builderAction);
     method public static androidx.collection.FloatLongMap emptyFloatLongMap();
     method public static androidx.collection.FloatLongMap floatLongMapOf();
     method public static androidx.collection.FloatLongMap floatLongMapOf(float key1, long value1);
@@ -432,6 +442,8 @@
   }
 
   public final class FloatObjectMapKt {
+    method public static inline <V> androidx.collection.FloatObjectMap<V> buildFloatObjectMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatObjectMap<V>,kotlin.Unit> builderAction);
+    method public static inline <V> androidx.collection.FloatObjectMap<V> buildFloatObjectMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatObjectMap<V>,kotlin.Unit> builderAction);
     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(float key1, V value1);
@@ -479,6 +491,8 @@
   }
 
   public final class FloatSetKt {
+    method public static inline androidx.collection.FloatSet buildFloatSet(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatSet,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.FloatSet buildFloatSet(kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatSet,kotlin.Unit> builderAction);
     method public static androidx.collection.FloatSet emptyFloatSet();
     method public static androidx.collection.FloatSet floatSetOf();
     method public static androidx.collection.FloatSet floatSetOf(float element1);
@@ -529,6 +543,8 @@
   }
 
   public final class IntFloatMapKt {
+    method public static inline androidx.collection.IntFloatMap buildIntFloatMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntFloatMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.IntFloatMap buildIntFloatMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntFloatMap,kotlin.Unit> builderAction);
     method public static androidx.collection.IntFloatMap emptyIntFloatMap();
     method public static androidx.collection.IntFloatMap intFloatMapOf();
     method public static androidx.collection.IntFloatMap intFloatMapOf(int key1, float value1);
@@ -581,6 +597,8 @@
   }
 
   public final class IntIntMapKt {
+    method public static inline androidx.collection.IntIntMap buildIntIntMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntIntMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.IntIntMap buildIntIntMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntIntMap,kotlin.Unit> builderAction);
     method public static androidx.collection.IntIntMap emptyIntIntMap();
     method public static androidx.collection.IntIntMap intIntMapOf();
     method public static androidx.collection.IntIntMap intIntMapOf(int key1, int value1);
@@ -661,6 +679,8 @@
   }
 
   public final class IntListKt {
+    method public static inline androidx.collection.IntList buildIntList(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntList,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.IntList buildIntList(kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntList,kotlin.Unit> builderAction);
     method public static androidx.collection.IntList emptyIntList();
     method public static androidx.collection.IntList intListOf();
     method public static androidx.collection.IntList intListOf(int element1);
@@ -711,6 +731,8 @@
   }
 
   public final class IntLongMapKt {
+    method public static inline androidx.collection.IntLongMap buildIntLongMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntLongMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.IntLongMap buildIntLongMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntLongMap,kotlin.Unit> builderAction);
     method public static androidx.collection.IntLongMap emptyIntLongMap();
     method public static androidx.collection.IntLongMap intLongMapOf();
     method public static androidx.collection.IntLongMap intLongMapOf(int key1, long value1);
@@ -763,6 +785,8 @@
   }
 
   public final class IntObjectMapKt {
+    method public static inline <V> androidx.collection.IntObjectMap<V> buildIntObjectMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntObjectMap<V>,kotlin.Unit> builderAction);
+    method public static inline <V> androidx.collection.IntObjectMap<V> buildIntObjectMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntObjectMap<V>,kotlin.Unit> builderAction);
     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(int key1, V value1);
@@ -810,6 +834,8 @@
   }
 
   public final class IntSetKt {
+    method public static inline androidx.collection.IntSet buildIntSet(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntSet,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.IntSet buildIntSet(kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntSet,kotlin.Unit> builderAction);
     method public static androidx.collection.IntSet emptyIntSet();
     method public static androidx.collection.IntSet intSetOf();
     method public static androidx.collection.IntSet intSetOf(int element1);
@@ -860,6 +886,8 @@
   }
 
   public final class LongFloatMapKt {
+    method public static inline androidx.collection.LongFloatMap buildLongFloatMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongFloatMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.LongFloatMap buildLongFloatMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongFloatMap,kotlin.Unit> builderAction);
     method public static androidx.collection.LongFloatMap emptyLongFloatMap();
     method public static androidx.collection.LongFloatMap longFloatMapOf();
     method public static androidx.collection.LongFloatMap longFloatMapOf(long key1, float value1);
@@ -912,6 +940,8 @@
   }
 
   public final class LongIntMapKt {
+    method public static inline androidx.collection.LongIntMap buildLongIntMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongIntMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.LongIntMap buildLongIntMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongIntMap,kotlin.Unit> builderAction);
     method public static androidx.collection.LongIntMap emptyLongIntMap();
     method public static androidx.collection.LongIntMap longIntMapOf();
     method public static androidx.collection.LongIntMap longIntMapOf(long key1, int value1);
@@ -981,6 +1011,8 @@
   }
 
   public final class LongListKt {
+    method public static inline androidx.collection.LongList buildLongList(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongList,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.LongList buildLongList(kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongList,kotlin.Unit> builderAction);
     method public static androidx.collection.LongList emptyLongList();
     method public static androidx.collection.LongList longListOf();
     method public static androidx.collection.LongList longListOf(long element1);
@@ -1031,6 +1063,8 @@
   }
 
   public final class LongLongMapKt {
+    method public static inline androidx.collection.LongLongMap buildLongLongMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongLongMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.LongLongMap buildLongLongMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongLongMap,kotlin.Unit> builderAction);
     method public static androidx.collection.LongLongMap emptyLongLongMap();
     method public static androidx.collection.LongLongMap longLongMapOf();
     method public static androidx.collection.LongLongMap longLongMapOf(long key1, long value1);
@@ -1093,6 +1127,8 @@
   }
 
   public final class LongObjectMapKt {
+    method public static inline <V> androidx.collection.LongObjectMap<V> buildLongObjectMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongObjectMap<V>,kotlin.Unit> builderAction);
+    method public static inline <V> androidx.collection.LongObjectMap<V> buildLongObjectMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongObjectMap<V>,kotlin.Unit> builderAction);
     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(long key1, V value1);
@@ -1140,6 +1176,8 @@
   }
 
   public final class LongSetKt {
+    method public static inline androidx.collection.LongSet buildLongSet(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongSet,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.LongSet buildLongSet(kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongSet,kotlin.Unit> builderAction);
     method public static androidx.collection.LongSet emptyLongSet();
     method public static androidx.collection.LongSet longSetOf();
     method public static androidx.collection.LongSet longSetOf(long element1);
@@ -1910,6 +1948,8 @@
   }
 
   public final class ObjectFloatMapKt {
+    method public static inline <K> androidx.collection.ObjectFloatMap<K> buildObjectFloatMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableObjectFloatMap<K>,kotlin.Unit> builderAction);
+    method public static inline <K> androidx.collection.ObjectFloatMap<K> buildObjectFloatMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableObjectFloatMap<K>,kotlin.Unit> builderAction);
     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(K key1, float value1);
@@ -1962,6 +2002,8 @@
   }
 
   public final class ObjectIntMapKt {
+    method public static inline <K> androidx.collection.ObjectIntMap<K> buildObjectIntMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableObjectIntMap<K>,kotlin.Unit> builderAction);
+    method public static inline <K> androidx.collection.ObjectIntMap<K> buildObjectIntMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableObjectIntMap<K>,kotlin.Unit> builderAction);
     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(K key1, int value1);
@@ -2081,6 +2123,8 @@
   }
 
   public final class ObjectLongMapKt {
+    method public static inline <K> androidx.collection.ObjectLongMap<K> buildObjectLongMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableObjectLongMap<K>,kotlin.Unit> builderAction);
+    method public static inline <K> androidx.collection.ObjectLongMap<K> buildObjectLongMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableObjectLongMap<K>,kotlin.Unit> builderAction);
     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(K key1, long value1);
diff --git a/collection/collection/api/restricted_current.txt b/collection/collection/api/restricted_current.txt
index 8b56e9b..9e01219 100644
--- a/collection/collection/api/restricted_current.txt
+++ b/collection/collection/api/restricted_current.txt
@@ -150,6 +150,8 @@
   }
 
   public final class DoubleListKt {
+    method public static inline androidx.collection.DoubleList buildDoubleList(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableDoubleList,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.DoubleList buildDoubleList(kotlin.jvm.functions.Function1<? super androidx.collection.MutableDoubleList,kotlin.Unit> builderAction);
     method public static androidx.collection.DoubleList doubleListOf();
     method public static androidx.collection.DoubleList doubleListOf(double element1);
     method public static androidx.collection.DoubleList doubleListOf(double element1, double element2);
@@ -205,6 +207,8 @@
   }
 
   public final class FloatFloatMapKt {
+    method public static inline androidx.collection.FloatFloatMap buildFloatFloatMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatFloatMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.FloatFloatMap buildFloatFloatMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatFloatMap,kotlin.Unit> builderAction);
     method public static androidx.collection.FloatFloatMap emptyFloatFloatMap();
     method public static androidx.collection.FloatFloatMap floatFloatMapOf();
     method public static androidx.collection.FloatFloatMap floatFloatMapOf(float key1, float value1);
@@ -273,6 +277,8 @@
   }
 
   public final class FloatIntMapKt {
+    method public static inline androidx.collection.FloatIntMap buildFloatIntMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatIntMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.FloatIntMap buildFloatIntMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatIntMap,kotlin.Unit> builderAction);
     method public static androidx.collection.FloatIntMap emptyFloatIntMap();
     method public static androidx.collection.FloatIntMap floatIntMapOf();
     method public static androidx.collection.FloatIntMap floatIntMapOf(float key1, int value1);
@@ -344,6 +350,8 @@
   }
 
   public final class FloatListKt {
+    method public static inline androidx.collection.FloatList buildFloatList(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatList,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.FloatList buildFloatList(kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatList,kotlin.Unit> builderAction);
     method public static androidx.collection.FloatList emptyFloatList();
     method public static androidx.collection.FloatList floatListOf();
     method public static androidx.collection.FloatList floatListOf(float element1);
@@ -399,6 +407,8 @@
   }
 
   public final class FloatLongMapKt {
+    method public static inline androidx.collection.FloatLongMap buildFloatLongMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatLongMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.FloatLongMap buildFloatLongMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatLongMap,kotlin.Unit> builderAction);
     method public static androidx.collection.FloatLongMap emptyFloatLongMap();
     method public static androidx.collection.FloatLongMap floatLongMapOf();
     method public static androidx.collection.FloatLongMap floatLongMapOf(float key1, long value1);
@@ -455,6 +465,8 @@
   }
 
   public final class FloatObjectMapKt {
+    method public static inline <V> androidx.collection.FloatObjectMap<V> buildFloatObjectMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatObjectMap<V>,kotlin.Unit> builderAction);
+    method public static inline <V> androidx.collection.FloatObjectMap<V> buildFloatObjectMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatObjectMap<V>,kotlin.Unit> builderAction);
     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(float key1, V value1);
@@ -505,6 +517,8 @@
   }
 
   public final class FloatSetKt {
+    method public static inline androidx.collection.FloatSet buildFloatSet(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatSet,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.FloatSet buildFloatSet(kotlin.jvm.functions.Function1<? super androidx.collection.MutableFloatSet,kotlin.Unit> builderAction);
     method public static androidx.collection.FloatSet emptyFloatSet();
     method public static androidx.collection.FloatSet floatSetOf();
     method public static androidx.collection.FloatSet floatSetOf(float element1);
@@ -560,6 +574,8 @@
   }
 
   public final class IntFloatMapKt {
+    method public static inline androidx.collection.IntFloatMap buildIntFloatMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntFloatMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.IntFloatMap buildIntFloatMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntFloatMap,kotlin.Unit> builderAction);
     method public static androidx.collection.IntFloatMap emptyIntFloatMap();
     method public static androidx.collection.IntFloatMap intFloatMapOf();
     method public static androidx.collection.IntFloatMap intFloatMapOf(int key1, float value1);
@@ -617,6 +633,8 @@
   }
 
   public final class IntIntMapKt {
+    method public static inline androidx.collection.IntIntMap buildIntIntMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntIntMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.IntIntMap buildIntIntMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntIntMap,kotlin.Unit> builderAction);
     method public static androidx.collection.IntIntMap emptyIntIntMap();
     method public static androidx.collection.IntIntMap intIntMapOf();
     method public static androidx.collection.IntIntMap intIntMapOf(int key1, int value1);
@@ -699,6 +717,8 @@
   }
 
   public final class IntListKt {
+    method public static inline androidx.collection.IntList buildIntList(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntList,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.IntList buildIntList(kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntList,kotlin.Unit> builderAction);
     method public static androidx.collection.IntList emptyIntList();
     method public static androidx.collection.IntList intListOf();
     method public static androidx.collection.IntList intListOf(int element1);
@@ -754,6 +774,8 @@
   }
 
   public final class IntLongMapKt {
+    method public static inline androidx.collection.IntLongMap buildIntLongMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntLongMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.IntLongMap buildIntLongMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntLongMap,kotlin.Unit> builderAction);
     method public static androidx.collection.IntLongMap emptyIntLongMap();
     method public static androidx.collection.IntLongMap intLongMapOf();
     method public static androidx.collection.IntLongMap intLongMapOf(int key1, long value1);
@@ -810,6 +832,8 @@
   }
 
   public final class IntObjectMapKt {
+    method public static inline <V> androidx.collection.IntObjectMap<V> buildIntObjectMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntObjectMap<V>,kotlin.Unit> builderAction);
+    method public static inline <V> androidx.collection.IntObjectMap<V> buildIntObjectMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntObjectMap<V>,kotlin.Unit> builderAction);
     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(int key1, V value1);
@@ -860,6 +884,8 @@
   }
 
   public final class IntSetKt {
+    method public static inline androidx.collection.IntSet buildIntSet(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntSet,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.IntSet buildIntSet(kotlin.jvm.functions.Function1<? super androidx.collection.MutableIntSet,kotlin.Unit> builderAction);
     method public static androidx.collection.IntSet emptyIntSet();
     method public static androidx.collection.IntSet intSetOf();
     method public static androidx.collection.IntSet intSetOf(int element1);
@@ -915,6 +941,8 @@
   }
 
   public final class LongFloatMapKt {
+    method public static inline androidx.collection.LongFloatMap buildLongFloatMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongFloatMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.LongFloatMap buildLongFloatMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongFloatMap,kotlin.Unit> builderAction);
     method public static androidx.collection.LongFloatMap emptyLongFloatMap();
     method public static androidx.collection.LongFloatMap longFloatMapOf();
     method public static androidx.collection.LongFloatMap longFloatMapOf(long key1, float value1);
@@ -972,6 +1000,8 @@
   }
 
   public final class LongIntMapKt {
+    method public static inline androidx.collection.LongIntMap buildLongIntMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongIntMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.LongIntMap buildLongIntMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongIntMap,kotlin.Unit> builderAction);
     method public static androidx.collection.LongIntMap emptyLongIntMap();
     method public static androidx.collection.LongIntMap longIntMapOf();
     method public static androidx.collection.LongIntMap longIntMapOf(long key1, int value1);
@@ -1043,6 +1073,8 @@
   }
 
   public final class LongListKt {
+    method public static inline androidx.collection.LongList buildLongList(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongList,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.LongList buildLongList(kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongList,kotlin.Unit> builderAction);
     method public static androidx.collection.LongList emptyLongList();
     method public static androidx.collection.LongList longListOf();
     method public static androidx.collection.LongList longListOf(long element1);
@@ -1098,6 +1130,8 @@
   }
 
   public final class LongLongMapKt {
+    method public static inline androidx.collection.LongLongMap buildLongLongMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongLongMap,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.LongLongMap buildLongLongMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongLongMap,kotlin.Unit> builderAction);
     method public static androidx.collection.LongLongMap emptyLongLongMap();
     method public static androidx.collection.LongLongMap longLongMapOf();
     method public static androidx.collection.LongLongMap longLongMapOf(long key1, long value1);
@@ -1164,6 +1198,8 @@
   }
 
   public final class LongObjectMapKt {
+    method public static inline <V> androidx.collection.LongObjectMap<V> buildLongObjectMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongObjectMap<V>,kotlin.Unit> builderAction);
+    method public static inline <V> androidx.collection.LongObjectMap<V> buildLongObjectMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongObjectMap<V>,kotlin.Unit> builderAction);
     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(long key1, V value1);
@@ -1214,6 +1250,8 @@
   }
 
   public final class LongSetKt {
+    method public static inline androidx.collection.LongSet buildLongSet(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongSet,kotlin.Unit> builderAction);
+    method public static inline androidx.collection.LongSet buildLongSet(kotlin.jvm.functions.Function1<? super androidx.collection.MutableLongSet,kotlin.Unit> builderAction);
     method public static androidx.collection.LongSet emptyLongSet();
     method public static androidx.collection.LongSet longSetOf();
     method public static androidx.collection.LongSet longSetOf(long element1);
@@ -2008,6 +2046,8 @@
   }
 
   public final class ObjectFloatMapKt {
+    method public static inline <K> androidx.collection.ObjectFloatMap<K> buildObjectFloatMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableObjectFloatMap<K>,kotlin.Unit> builderAction);
+    method public static inline <K> androidx.collection.ObjectFloatMap<K> buildObjectFloatMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableObjectFloatMap<K>,kotlin.Unit> builderAction);
     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(K key1, float value1);
@@ -2065,6 +2105,8 @@
   }
 
   public final class ObjectIntMapKt {
+    method public static inline <K> androidx.collection.ObjectIntMap<K> buildObjectIntMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableObjectIntMap<K>,kotlin.Unit> builderAction);
+    method public static inline <K> androidx.collection.ObjectIntMap<K> buildObjectIntMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableObjectIntMap<K>,kotlin.Unit> builderAction);
     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(K key1, int value1);
@@ -2191,6 +2233,8 @@
   }
 
   public final class ObjectLongMapKt {
+    method public static inline <K> androidx.collection.ObjectLongMap<K> buildObjectLongMap(int initialCapacity, kotlin.jvm.functions.Function1<? super androidx.collection.MutableObjectLongMap<K>,kotlin.Unit> builderAction);
+    method public static inline <K> androidx.collection.ObjectLongMap<K> buildObjectLongMap(kotlin.jvm.functions.Function1<? super androidx.collection.MutableObjectLongMap<K>,kotlin.Unit> builderAction);
     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(K key1, long value1);
diff --git a/collection/collection/bcv/native/current.txt b/collection/collection/bcv/native/current.txt
index 38ae0962..86e12d2 100644
--- a/collection/collection/bcv/native/current.txt
+++ b/collection/collection/bcv/native/current.txt
@@ -2493,9 +2493,53 @@
 final inline fun <#A: kotlin/Any?> (androidx.collection/SparseArrayCompat<#A>).androidx.collection/isNotEmpty(): kotlin/Boolean // androidx.collection/isNotEmpty|[email protected]<0:0>(){0§<kotlin.Any?>}[0]
 final inline fun <#A: kotlin/Any?> (androidx.collection/SparseArrayCompat<#A>).androidx.collection/set(kotlin/Int, #A) // androidx.collection/set|[email protected]<0:0>(kotlin.Int;0:0){0§<kotlin.Any?>}[0]
 final inline fun <#A: kotlin/Any?> androidx.collection/arraySetOf(): androidx.collection/ArraySet<#A> // androidx.collection/arraySetOf|arraySetOf(){0§<kotlin.Any?>}[0]
+final inline fun <#A: kotlin/Any?> androidx.collection/buildFloatObjectMap(kotlin/Function1<androidx.collection/MutableFloatObjectMap<#A>, kotlin/Unit>): androidx.collection/FloatObjectMap<#A> // androidx.collection/buildFloatObjectMap|buildFloatObjectMap(kotlin.Function1<androidx.collection.MutableFloatObjectMap<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
+final inline fun <#A: kotlin/Any?> androidx.collection/buildFloatObjectMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableFloatObjectMap<#A>, kotlin/Unit>): androidx.collection/FloatObjectMap<#A> // androidx.collection/buildFloatObjectMap|buildFloatObjectMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableFloatObjectMap<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
+final inline fun <#A: kotlin/Any?> androidx.collection/buildIntObjectMap(kotlin/Function1<androidx.collection/MutableIntObjectMap<#A>, kotlin/Unit>): androidx.collection/IntObjectMap<#A> // androidx.collection/buildIntObjectMap|buildIntObjectMap(kotlin.Function1<androidx.collection.MutableIntObjectMap<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
+final inline fun <#A: kotlin/Any?> androidx.collection/buildIntObjectMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableIntObjectMap<#A>, kotlin/Unit>): androidx.collection/IntObjectMap<#A> // androidx.collection/buildIntObjectMap|buildIntObjectMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableIntObjectMap<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
+final inline fun <#A: kotlin/Any?> androidx.collection/buildLongObjectMap(kotlin/Function1<androidx.collection/MutableLongObjectMap<#A>, kotlin/Unit>): androidx.collection/LongObjectMap<#A> // androidx.collection/buildLongObjectMap|buildLongObjectMap(kotlin.Function1<androidx.collection.MutableLongObjectMap<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
+final inline fun <#A: kotlin/Any?> androidx.collection/buildLongObjectMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableLongObjectMap<#A>, kotlin/Unit>): androidx.collection/LongObjectMap<#A> // androidx.collection/buildLongObjectMap|buildLongObjectMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableLongObjectMap<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
+final inline fun <#A: kotlin/Any?> androidx.collection/buildObjectFloatMap(kotlin/Function1<androidx.collection/MutableObjectFloatMap<#A>, kotlin/Unit>): androidx.collection/ObjectFloatMap<#A> // androidx.collection/buildObjectFloatMap|buildObjectFloatMap(kotlin.Function1<androidx.collection.MutableObjectFloatMap<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
+final inline fun <#A: kotlin/Any?> androidx.collection/buildObjectFloatMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableObjectFloatMap<#A>, kotlin/Unit>): androidx.collection/ObjectFloatMap<#A> // androidx.collection/buildObjectFloatMap|buildObjectFloatMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableObjectFloatMap<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
+final inline fun <#A: kotlin/Any?> androidx.collection/buildObjectIntMap(kotlin/Function1<androidx.collection/MutableObjectIntMap<#A>, kotlin/Unit>): androidx.collection/ObjectIntMap<#A> // androidx.collection/buildObjectIntMap|buildObjectIntMap(kotlin.Function1<androidx.collection.MutableObjectIntMap<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
+final inline fun <#A: kotlin/Any?> androidx.collection/buildObjectIntMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableObjectIntMap<#A>, kotlin/Unit>): androidx.collection/ObjectIntMap<#A> // androidx.collection/buildObjectIntMap|buildObjectIntMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableObjectIntMap<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
+final inline fun <#A: kotlin/Any?> androidx.collection/buildObjectLongMap(kotlin/Function1<androidx.collection/MutableObjectLongMap<#A>, kotlin/Unit>): androidx.collection/ObjectLongMap<#A> // androidx.collection/buildObjectLongMap|buildObjectLongMap(kotlin.Function1<androidx.collection.MutableObjectLongMap<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
+final inline fun <#A: kotlin/Any?> androidx.collection/buildObjectLongMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableObjectLongMap<#A>, kotlin/Unit>): androidx.collection/ObjectLongMap<#A> // androidx.collection/buildObjectLongMap|buildObjectLongMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableObjectLongMap<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
 final inline fun <#A: kotlin/Any?> androidx.collection/mutableObjectListOf(): androidx.collection/MutableObjectList<#A> // androidx.collection/mutableObjectListOf|mutableObjectListOf(){0§<kotlin.Any?>}[0]
 final inline fun <#A: kotlin/Any?> androidx.collection/mutableObjectListOf(kotlin/Array<out #A>...): androidx.collection/MutableObjectList<#A> // androidx.collection/mutableObjectListOf|mutableObjectListOf(kotlin.Array<out|0:0>...){0§<kotlin.Any?>}[0]
 final inline fun androidx.collection.internal/floatFromBits(kotlin/Int): kotlin/Float // androidx.collection.internal/floatFromBits|floatFromBits(kotlin.Int){}[0]
+final inline fun androidx.collection/buildDoubleList(kotlin/Function1<androidx.collection/MutableDoubleList, kotlin/Unit>): androidx.collection/DoubleList // androidx.collection/buildDoubleList|buildDoubleList(kotlin.Function1<androidx.collection.MutableDoubleList,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildDoubleList(kotlin/Int, kotlin/Function1<androidx.collection/MutableDoubleList, kotlin/Unit>): androidx.collection/DoubleList // androidx.collection/buildDoubleList|buildDoubleList(kotlin.Int;kotlin.Function1<androidx.collection.MutableDoubleList,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildFloatFloatMap(kotlin/Function1<androidx.collection/MutableFloatFloatMap, kotlin/Unit>): androidx.collection/FloatFloatMap // androidx.collection/buildFloatFloatMap|buildFloatFloatMap(kotlin.Function1<androidx.collection.MutableFloatFloatMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildFloatFloatMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableFloatFloatMap, kotlin/Unit>): androidx.collection/FloatFloatMap // androidx.collection/buildFloatFloatMap|buildFloatFloatMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableFloatFloatMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildFloatIntMap(kotlin/Function1<androidx.collection/MutableFloatIntMap, kotlin/Unit>): androidx.collection/FloatIntMap // androidx.collection/buildFloatIntMap|buildFloatIntMap(kotlin.Function1<androidx.collection.MutableFloatIntMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildFloatIntMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableFloatIntMap, kotlin/Unit>): androidx.collection/FloatIntMap // androidx.collection/buildFloatIntMap|buildFloatIntMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableFloatIntMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildFloatList(kotlin/Function1<androidx.collection/MutableFloatList, kotlin/Unit>): androidx.collection/FloatList // androidx.collection/buildFloatList|buildFloatList(kotlin.Function1<androidx.collection.MutableFloatList,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildFloatList(kotlin/Int, kotlin/Function1<androidx.collection/MutableFloatList, kotlin/Unit>): androidx.collection/FloatList // androidx.collection/buildFloatList|buildFloatList(kotlin.Int;kotlin.Function1<androidx.collection.MutableFloatList,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildFloatLongMap(kotlin/Function1<androidx.collection/MutableFloatLongMap, kotlin/Unit>): androidx.collection/FloatLongMap // androidx.collection/buildFloatLongMap|buildFloatLongMap(kotlin.Function1<androidx.collection.MutableFloatLongMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildFloatLongMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableFloatLongMap, kotlin/Unit>): androidx.collection/FloatLongMap // androidx.collection/buildFloatLongMap|buildFloatLongMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableFloatLongMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildFloatSet(kotlin/Function1<androidx.collection/MutableFloatSet, kotlin/Unit>): androidx.collection/FloatSet // androidx.collection/buildFloatSet|buildFloatSet(kotlin.Function1<androidx.collection.MutableFloatSet,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildFloatSet(kotlin/Int, kotlin/Function1<androidx.collection/MutableFloatSet, kotlin/Unit>): androidx.collection/FloatSet // androidx.collection/buildFloatSet|buildFloatSet(kotlin.Int;kotlin.Function1<androidx.collection.MutableFloatSet,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildIntFloatMap(kotlin/Function1<androidx.collection/MutableIntFloatMap, kotlin/Unit>): androidx.collection/IntFloatMap // androidx.collection/buildIntFloatMap|buildIntFloatMap(kotlin.Function1<androidx.collection.MutableIntFloatMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildIntFloatMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableIntFloatMap, kotlin/Unit>): androidx.collection/IntFloatMap // androidx.collection/buildIntFloatMap|buildIntFloatMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableIntFloatMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildIntIntMap(kotlin/Function1<androidx.collection/MutableIntIntMap, kotlin/Unit>): androidx.collection/IntIntMap // androidx.collection/buildIntIntMap|buildIntIntMap(kotlin.Function1<androidx.collection.MutableIntIntMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildIntIntMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableIntIntMap, kotlin/Unit>): androidx.collection/IntIntMap // androidx.collection/buildIntIntMap|buildIntIntMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableIntIntMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildIntList(kotlin/Function1<androidx.collection/MutableIntList, kotlin/Unit>): androidx.collection/IntList // androidx.collection/buildIntList|buildIntList(kotlin.Function1<androidx.collection.MutableIntList,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildIntList(kotlin/Int, kotlin/Function1<androidx.collection/MutableIntList, kotlin/Unit>): androidx.collection/IntList // androidx.collection/buildIntList|buildIntList(kotlin.Int;kotlin.Function1<androidx.collection.MutableIntList,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildIntLongMap(kotlin/Function1<androidx.collection/MutableIntLongMap, kotlin/Unit>): androidx.collection/IntLongMap // androidx.collection/buildIntLongMap|buildIntLongMap(kotlin.Function1<androidx.collection.MutableIntLongMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildIntLongMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableIntLongMap, kotlin/Unit>): androidx.collection/IntLongMap // androidx.collection/buildIntLongMap|buildIntLongMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableIntLongMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildIntSet(kotlin/Function1<androidx.collection/MutableIntSet, kotlin/Unit>): androidx.collection/IntSet // androidx.collection/buildIntSet|buildIntSet(kotlin.Function1<androidx.collection.MutableIntSet,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildIntSet(kotlin/Int, kotlin/Function1<androidx.collection/MutableIntSet, kotlin/Unit>): androidx.collection/IntSet // androidx.collection/buildIntSet|buildIntSet(kotlin.Int;kotlin.Function1<androidx.collection.MutableIntSet,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildLongFloatMap(kotlin/Function1<androidx.collection/MutableLongFloatMap, kotlin/Unit>): androidx.collection/LongFloatMap // androidx.collection/buildLongFloatMap|buildLongFloatMap(kotlin.Function1<androidx.collection.MutableLongFloatMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildLongFloatMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableLongFloatMap, kotlin/Unit>): androidx.collection/LongFloatMap // androidx.collection/buildLongFloatMap|buildLongFloatMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableLongFloatMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildLongIntMap(kotlin/Function1<androidx.collection/MutableLongIntMap, kotlin/Unit>): androidx.collection/LongIntMap // androidx.collection/buildLongIntMap|buildLongIntMap(kotlin.Function1<androidx.collection.MutableLongIntMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildLongIntMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableLongIntMap, kotlin/Unit>): androidx.collection/LongIntMap // androidx.collection/buildLongIntMap|buildLongIntMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableLongIntMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildLongList(kotlin/Function1<androidx.collection/MutableLongList, kotlin/Unit>): androidx.collection/LongList // androidx.collection/buildLongList|buildLongList(kotlin.Function1<androidx.collection.MutableLongList,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildLongList(kotlin/Int, kotlin/Function1<androidx.collection/MutableLongList, kotlin/Unit>): androidx.collection/LongList // androidx.collection/buildLongList|buildLongList(kotlin.Int;kotlin.Function1<androidx.collection.MutableLongList,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildLongLongMap(kotlin/Function1<androidx.collection/MutableLongLongMap, kotlin/Unit>): androidx.collection/LongLongMap // androidx.collection/buildLongLongMap|buildLongLongMap(kotlin.Function1<androidx.collection.MutableLongLongMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildLongLongMap(kotlin/Int, kotlin/Function1<androidx.collection/MutableLongLongMap, kotlin/Unit>): androidx.collection/LongLongMap // androidx.collection/buildLongLongMap|buildLongLongMap(kotlin.Int;kotlin.Function1<androidx.collection.MutableLongLongMap,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildLongSet(kotlin/Function1<androidx.collection/MutableLongSet, kotlin/Unit>): androidx.collection/LongSet // androidx.collection/buildLongSet|buildLongSet(kotlin.Function1<androidx.collection.MutableLongSet,kotlin.Unit>){}[0]
+final inline fun androidx.collection/buildLongSet(kotlin/Int, kotlin/Function1<androidx.collection/MutableLongSet, kotlin/Unit>): androidx.collection/LongSet // androidx.collection/buildLongSet|buildLongSet(kotlin.Int;kotlin.Function1<androidx.collection.MutableLongSet,kotlin.Unit>){}[0]
 final inline fun androidx.collection/isFull(kotlin/Long): kotlin/Boolean // androidx.collection/isFull|isFull(kotlin.Long){}[0]
 final inline fun androidx.collection/mutableDoubleListOf(): androidx.collection/MutableDoubleList // androidx.collection/mutableDoubleListOf|mutableDoubleListOf(){}[0]
 final inline fun androidx.collection/mutableDoubleListOf(kotlin/DoubleArray...): androidx.collection/MutableDoubleList // androidx.collection/mutableDoubleListOf|mutableDoubleListOf(kotlin.DoubleArray...){}[0]
diff --git a/collection/collection/build.gradle b/collection/collection/build.gradle
index 8e38f60..7900e3e 100644
--- a/collection/collection/build.gradle
+++ b/collection/collection/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import androidx.build.PlatformIdentifier
 import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
@@ -147,4 +149,5 @@
     inceptionYear = "2018"
     description = "Standalone efficient collections."
     metalavaK2UastEnabled = false
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/DoubleList.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/DoubleList.kt
index 5b23890..b213ddc 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/DoubleList.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/DoubleList.kt
@@ -23,6 +23,7 @@
 import androidx.collection.internal.throwIndexOutOfBoundsException
 import androidx.collection.internal.throwNoSuchElementException
 import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
@@ -947,3 +948,35 @@
 /** @return a new [MutableDoubleList] with the given elements, in order. */
 public inline fun mutableDoubleListOf(vararg elements: Double): MutableDoubleList =
     MutableDoubleList(elements.size).apply { plusAssign(elements) }
+
+/**
+ * Builds a new [DoubleList] by populating a [MutableDoubleList] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableDoubleList] can be populated.
+ */
+public inline fun buildDoubleList(
+    builderAction: MutableDoubleList.() -> Unit,
+): DoubleList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableDoubleList().apply(builderAction)
+}
+
+/**
+ * Builds a new [DoubleList] by populating a [MutableDoubleList] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableDoubleList] can be populated.
+ */
+public inline fun buildDoubleList(
+    initialCapacity: Int,
+    builderAction: MutableDoubleList.() -> Unit,
+): DoubleList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableDoubleList(initialCapacity).apply(builderAction)
+}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatFloatMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatFloatMap.kt
index 072e286..3e9f57b 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatFloatMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatFloatMap.kt
@@ -15,11 +15,15 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -212,6 +216,40 @@
     }
 
 /**
+ * Builds a new [FloatFloatMap] by populating a [MutableFloatFloatMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableFloatFloatMap] can be populated.
+ */
+public inline fun buildFloatFloatMap(
+    builderAction: MutableFloatFloatMap.() -> Unit,
+): FloatFloatMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableFloatFloatMap().apply(builderAction)
+}
+
+/**
+ * Builds a new [FloatFloatMap] by populating a [MutableFloatFloatMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableFloatFloatMap] can be populated.
+ */
+public inline fun buildFloatFloatMap(
+    initialCapacity: Int,
+    builderAction: MutableFloatFloatMap.() -> Unit,
+): FloatFloatMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableFloatFloatMap(initialCapacity).apply(builderAction)
+}
+
+/**
  * [FloatFloatMap] is a container with a [Map]-like interface for [Float] primitive keys and [Float]
  * primitive values.
  *
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatIntMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatIntMap.kt
index 1ff0eee..0c21a77 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatIntMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatIntMap.kt
@@ -15,11 +15,15 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -212,6 +216,38 @@
     }
 
 /**
+ * Builds a new [FloatIntMap] by populating a [MutableFloatIntMap] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableFloatIntMap] can be populated.
+ */
+public inline fun buildFloatIntMap(
+    builderAction: MutableFloatIntMap.() -> Unit,
+): FloatIntMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableFloatIntMap().apply(builderAction)
+}
+
+/**
+ * Builds a new [FloatIntMap] by populating a [MutableFloatIntMap] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableFloatIntMap] can be populated.
+ */
+public inline fun buildFloatIntMap(
+    initialCapacity: Int,
+    builderAction: MutableFloatIntMap.() -> Unit,
+): FloatIntMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableFloatIntMap(initialCapacity).apply(builderAction)
+}
+
+/**
  * [FloatIntMap] is a container with a [Map]-like interface for [Float] primitive keys and [Int]
  * primitive values.
  *
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatList.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatList.kt
index 3bf1ac3..a35ba55 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatList.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatList.kt
@@ -23,6 +23,7 @@
 import androidx.collection.internal.throwIndexOutOfBoundsException
 import androidx.collection.internal.throwNoSuchElementException
 import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
@@ -940,3 +941,35 @@
 /** @return a new [MutableFloatList] with the given elements, in order. */
 public inline fun mutableFloatListOf(vararg elements: Float): MutableFloatList =
     MutableFloatList(elements.size).apply { plusAssign(elements) }
+
+/**
+ * Builds a new [FloatList] by populating a [MutableFloatList] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableFloatList] can be populated.
+ */
+public inline fun buildFloatList(
+    builderAction: MutableFloatList.() -> Unit,
+): FloatList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableFloatList().apply(builderAction)
+}
+
+/**
+ * Builds a new [FloatList] by populating a [MutableFloatList] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableFloatList] can be populated.
+ */
+public inline fun buildFloatList(
+    initialCapacity: Int,
+    builderAction: MutableFloatList.() -> Unit,
+): FloatList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableFloatList(initialCapacity).apply(builderAction)
+}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatLongMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatLongMap.kt
index a60cdc7..c5ad874 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatLongMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatLongMap.kt
@@ -15,11 +15,15 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -212,6 +216,40 @@
     }
 
 /**
+ * Builds a new [FloatLongMap] by populating a [MutableFloatLongMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableFloatLongMap] can be populated.
+ */
+public inline fun buildFloatLongMap(
+    builderAction: MutableFloatLongMap.() -> Unit,
+): FloatLongMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableFloatLongMap().apply(builderAction)
+}
+
+/**
+ * Builds a new [FloatLongMap] by populating a [MutableFloatLongMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableFloatLongMap] can be populated.
+ */
+public inline fun buildFloatLongMap(
+    initialCapacity: Int,
+    builderAction: MutableFloatLongMap.() -> Unit,
+): FloatLongMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableFloatLongMap(initialCapacity).apply(builderAction)
+}
+
+/**
  * [FloatLongMap] is a container with a [Map]-like interface for [Float] primitive keys and [Long]
  * primitive values.
  *
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatObjectMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatObjectMap.kt
index bdb784d..4277962a 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatObjectMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatObjectMap.kt
@@ -15,11 +15,15 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.EMPTY_OBJECTS
 import androidx.collection.internal.requirePrecondition
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -214,6 +218,40 @@
     }
 
 /**
+ * Builds a new [FloatObjectMap] by populating a [MutableFloatObjectMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableFloatObjectMap] can be populated.
+ */
+public inline fun <V> buildFloatObjectMap(
+    builderAction: MutableFloatObjectMap<V>.() -> Unit,
+): FloatObjectMap<V> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableFloatObjectMap<V>().apply(builderAction)
+}
+
+/**
+ * Builds a new [FloatObjectMap] by populating a [MutableFloatObjectMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableFloatObjectMap] can be populated.
+ */
+public inline fun <V> buildFloatObjectMap(
+    initialCapacity: Int,
+    builderAction: MutableFloatObjectMap<V>.() -> Unit,
+): FloatObjectMap<V> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableFloatObjectMap<V>(initialCapacity).apply(builderAction)
+}
+
+/**
  * [FloatObjectMap] is a container with a [Map]-like interface for keys with [Float] primitives and
  * reference type values.
  *
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatSet.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatSet.kt
index 0279ff4..bc8473b 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/FloatSet.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/FloatSet.kt
@@ -23,11 +23,14 @@
     "PrivatePropertyName",
     "NOTHING_TO_INLINE"
 )
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.annotation.IntRange
 import androidx.collection.internal.requirePrecondition
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
@@ -100,6 +103,38 @@
     MutableFloatSet(elements.size).apply { plusAssign(elements) }
 
 /**
+ * Builds a new [FloatSet] by populating a [MutableFloatSet] using the given [builderAction].
+ *
+ * The set passed as a receiver to the [builderAction] is valid only inside that function. Using it
+ * outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableFloatSet] can be populated.
+ */
+public inline fun buildFloatSet(
+    builderAction: MutableFloatSet.() -> Unit,
+): FloatSet {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableFloatSet().apply(builderAction)
+}
+
+/**
+ * Builds a new [FloatSet] by populating a [MutableFloatSet] using the given [builderAction].
+ *
+ * The set passed as a receiver to the [builderAction] is valid only inside that function. Using it
+ * outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableFloatSet] can be populated.
+ */
+public inline fun buildFloatSet(
+    initialCapacity: Int,
+    builderAction: MutableFloatSet.() -> Unit,
+): FloatSet {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableFloatSet(initialCapacity).apply(builderAction)
+}
+
+/**
  * [FloatSet] is a container with a [Set]-like interface designed to avoid allocations, including
  * boxing.
  *
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntFloatMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntFloatMap.kt
index 10225ca..59c3f2c 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/IntFloatMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntFloatMap.kt
@@ -15,11 +15,15 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -212,6 +216,38 @@
     }
 
 /**
+ * Builds a new [IntFloatMap] by populating a [MutableIntFloatMap] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableIntFloatMap] can be populated.
+ */
+public inline fun buildIntFloatMap(
+    builderAction: MutableIntFloatMap.() -> Unit,
+): IntFloatMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableIntFloatMap().apply(builderAction)
+}
+
+/**
+ * Builds a new [IntFloatMap] by populating a [MutableIntFloatMap] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableIntFloatMap] can be populated.
+ */
+public inline fun buildIntFloatMap(
+    initialCapacity: Int,
+    builderAction: MutableIntFloatMap.() -> Unit,
+): IntFloatMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableIntFloatMap(initialCapacity).apply(builderAction)
+}
+
+/**
  * [IntFloatMap] is a container with a [Map]-like interface for [Int] primitive keys and [Float]
  * primitive values.
  *
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntIntMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntIntMap.kt
index 9f98b11..0b7d028 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/IntIntMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntIntMap.kt
@@ -15,11 +15,15 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -212,6 +216,38 @@
     }
 
 /**
+ * Builds a new [IntIntMap] by populating a [MutableIntIntMap] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableIntIntMap] can be populated.
+ */
+public inline fun buildIntIntMap(
+    builderAction: MutableIntIntMap.() -> Unit,
+): IntIntMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableIntIntMap().apply(builderAction)
+}
+
+/**
+ * Builds a new [IntIntMap] by populating a [MutableIntIntMap] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableIntIntMap] can be populated.
+ */
+public inline fun buildIntIntMap(
+    initialCapacity: Int,
+    builderAction: MutableIntIntMap.() -> Unit,
+): IntIntMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableIntIntMap(initialCapacity).apply(builderAction)
+}
+
+/**
  * [IntIntMap] is a container with a [Map]-like interface for [Int] primitive keys and [Int]
  * primitive values.
  *
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntList.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntList.kt
index a4e6036..0edb357 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/IntList.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntList.kt
@@ -23,6 +23,7 @@
 import androidx.collection.internal.throwIndexOutOfBoundsException
 import androidx.collection.internal.throwNoSuchElementException
 import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
@@ -932,3 +933,35 @@
 /** @return a new [MutableIntList] with the given elements, in order. */
 public inline fun mutableIntListOf(vararg elements: Int): MutableIntList =
     MutableIntList(elements.size).apply { plusAssign(elements) }
+
+/**
+ * Builds a new [IntList] by populating a [MutableIntList] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableIntList] can be populated.
+ */
+public inline fun buildIntList(
+    builderAction: MutableIntList.() -> Unit,
+): IntList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableIntList().apply(builderAction)
+}
+
+/**
+ * Builds a new [IntList] by populating a [MutableIntList] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableIntList] can be populated.
+ */
+public inline fun buildIntList(
+    initialCapacity: Int,
+    builderAction: MutableIntList.() -> Unit,
+): IntList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableIntList(initialCapacity).apply(builderAction)
+}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntLongMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntLongMap.kt
index dedc3ae..7cfd67c 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/IntLongMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntLongMap.kt
@@ -15,11 +15,15 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -212,6 +216,38 @@
     }
 
 /**
+ * Builds a new [IntLongMap] by populating a [MutableIntLongMap] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableIntLongMap] can be populated.
+ */
+public inline fun buildIntLongMap(
+    builderAction: MutableIntLongMap.() -> Unit,
+): IntLongMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableIntLongMap().apply(builderAction)
+}
+
+/**
+ * Builds a new [IntLongMap] by populating a [MutableIntLongMap] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableIntLongMap] can be populated.
+ */
+public inline fun buildIntLongMap(
+    initialCapacity: Int,
+    builderAction: MutableIntLongMap.() -> Unit,
+): IntLongMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableIntLongMap(initialCapacity).apply(builderAction)
+}
+
+/**
  * [IntLongMap] is a container with a [Map]-like interface for [Int] primitive keys and [Long]
  * primitive values.
  *
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntObjectMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntObjectMap.kt
index ceab287..30a363c 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/IntObjectMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntObjectMap.kt
@@ -15,11 +15,15 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.EMPTY_OBJECTS
 import androidx.collection.internal.requirePrecondition
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -214,6 +218,40 @@
     }
 
 /**
+ * Builds a new [IntObjectMap] by populating a [MutableIntObjectMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableIntObjectMap] can be populated.
+ */
+public inline fun <V> buildIntObjectMap(
+    builderAction: MutableIntObjectMap<V>.() -> Unit,
+): IntObjectMap<V> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableIntObjectMap<V>().apply(builderAction)
+}
+
+/**
+ * Builds a new [IntObjectMap] by populating a [MutableIntObjectMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableIntObjectMap] can be populated.
+ */
+public inline fun <V> buildIntObjectMap(
+    initialCapacity: Int,
+    builderAction: MutableIntObjectMap<V>.() -> Unit,
+): IntObjectMap<V> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableIntObjectMap<V>(initialCapacity).apply(builderAction)
+}
+
+/**
  * [IntObjectMap] is a container with a [Map]-like interface for keys with [Int] primitives and
  * reference type values.
  *
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/IntSet.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/IntSet.kt
index 036f203..ac70e0e 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/IntSet.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/IntSet.kt
@@ -23,11 +23,14 @@
     "PrivatePropertyName",
     "NOTHING_TO_INLINE"
 )
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.annotation.IntRange
 import androidx.collection.internal.requirePrecondition
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
@@ -98,6 +101,38 @@
     MutableIntSet(elements.size).apply { plusAssign(elements) }
 
 /**
+ * Builds a new [IntSet] by populating a [MutableIntSet] using the given [builderAction].
+ *
+ * The set passed as a receiver to the [builderAction] is valid only inside that function. Using it
+ * outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableIntSet] can be populated.
+ */
+public inline fun buildIntSet(
+    builderAction: MutableIntSet.() -> Unit,
+): IntSet {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableIntSet().apply(builderAction)
+}
+
+/**
+ * Builds a new [IntSet] by populating a [MutableIntSet] using the given [builderAction].
+ *
+ * The set passed as a receiver to the [builderAction] is valid only inside that function. Using it
+ * outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableIntSet] can be populated.
+ */
+public inline fun buildIntSet(
+    initialCapacity: Int,
+    builderAction: MutableIntSet.() -> Unit,
+): IntSet {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableIntSet(initialCapacity).apply(builderAction)
+}
+
+/**
  * [IntSet] is a container with a [Set]-like interface designed to avoid allocations, including
  * boxing.
  *
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongFloatMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongFloatMap.kt
index bb34619..7c05cc0 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/LongFloatMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongFloatMap.kt
@@ -15,11 +15,15 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -212,6 +216,40 @@
     }
 
 /**
+ * Builds a new [LongFloatMap] by populating a [MutableLongFloatMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableLongFloatMap] can be populated.
+ */
+public inline fun buildLongFloatMap(
+    builderAction: MutableLongFloatMap.() -> Unit,
+): LongFloatMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableLongFloatMap().apply(builderAction)
+}
+
+/**
+ * Builds a new [LongFloatMap] by populating a [MutableLongFloatMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableLongFloatMap] can be populated.
+ */
+public inline fun buildLongFloatMap(
+    initialCapacity: Int,
+    builderAction: MutableLongFloatMap.() -> Unit,
+): LongFloatMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableLongFloatMap(initialCapacity).apply(builderAction)
+}
+
+/**
  * [LongFloatMap] is a container with a [Map]-like interface for [Long] primitive keys and [Float]
  * primitive values.
  *
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongIntMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongIntMap.kt
index a9dc213..873f958 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/LongIntMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongIntMap.kt
@@ -15,11 +15,15 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -212,6 +216,38 @@
     }
 
 /**
+ * Builds a new [LongIntMap] by populating a [MutableLongIntMap] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableLongIntMap] can be populated.
+ */
+public inline fun buildLongIntMap(
+    builderAction: MutableLongIntMap.() -> Unit,
+): LongIntMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableLongIntMap().apply(builderAction)
+}
+
+/**
+ * Builds a new [LongIntMap] by populating a [MutableLongIntMap] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableLongIntMap] can be populated.
+ */
+public inline fun buildLongIntMap(
+    initialCapacity: Int,
+    builderAction: MutableLongIntMap.() -> Unit,
+): LongIntMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableLongIntMap(initialCapacity).apply(builderAction)
+}
+
+/**
  * [LongIntMap] is a container with a [Map]-like interface for [Long] primitive keys and [Int]
  * primitive values.
  *
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongList.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongList.kt
index 8f4a32e..927f1f0 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/LongList.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongList.kt
@@ -23,6 +23,7 @@
 import androidx.collection.internal.throwIndexOutOfBoundsException
 import androidx.collection.internal.throwNoSuchElementException
 import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
@@ -936,3 +937,35 @@
 /** @return a new [MutableLongList] with the given elements, in order. */
 public inline fun mutableLongListOf(vararg elements: Long): MutableLongList =
     MutableLongList(elements.size).apply { plusAssign(elements) }
+
+/**
+ * Builds a new [LongList] by populating a [MutableLongList] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableLongList] can be populated.
+ */
+public inline fun buildLongList(
+    builderAction: MutableLongList.() -> Unit,
+): LongList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableLongList().apply(builderAction)
+}
+
+/**
+ * Builds a new [LongList] by populating a [MutableLongList] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableLongList] can be populated.
+ */
+public inline fun buildLongList(
+    initialCapacity: Int,
+    builderAction: MutableLongList.() -> Unit,
+): LongList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableLongList(initialCapacity).apply(builderAction)
+}
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongLongMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongLongMap.kt
index dbe6771..f13c10b 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/LongLongMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongLongMap.kt
@@ -15,11 +15,15 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -212,6 +216,38 @@
     }
 
 /**
+ * Builds a new [LongLongMap] by populating a [MutableLongLongMap] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableLongLongMap] can be populated.
+ */
+public inline fun buildLongLongMap(
+    builderAction: MutableLongLongMap.() -> Unit,
+): LongLongMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableLongLongMap().apply(builderAction)
+}
+
+/**
+ * Builds a new [LongLongMap] by populating a [MutableLongLongMap] using the given [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableLongLongMap] can be populated.
+ */
+public inline fun buildLongLongMap(
+    initialCapacity: Int,
+    builderAction: MutableLongLongMap.() -> Unit,
+): LongLongMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableLongLongMap(initialCapacity).apply(builderAction)
+}
+
+/**
  * [LongLongMap] is a container with a [Map]-like interface for [Long] primitive keys and [Long]
  * primitive values.
  *
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongObjectMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongObjectMap.kt
index eb674d9..fe2d524 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/LongObjectMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongObjectMap.kt
@@ -15,11 +15,15 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.EMPTY_OBJECTS
 import androidx.collection.internal.requirePrecondition
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -214,6 +218,40 @@
     }
 
 /**
+ * Builds a new [LongObjectMap] by populating a [MutableLongObjectMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableLongObjectMap] can be populated.
+ */
+public inline fun <V> buildLongObjectMap(
+    builderAction: MutableLongObjectMap<V>.() -> Unit,
+): LongObjectMap<V> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableLongObjectMap<V>().apply(builderAction)
+}
+
+/**
+ * Builds a new [LongObjectMap] by populating a [MutableLongObjectMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableLongObjectMap] can be populated.
+ */
+public inline fun <V> buildLongObjectMap(
+    initialCapacity: Int,
+    builderAction: MutableLongObjectMap<V>.() -> Unit,
+): LongObjectMap<V> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableLongObjectMap<V>(initialCapacity).apply(builderAction)
+}
+
+/**
  * [LongObjectMap] is a container with a [Map]-like interface for keys with [Long] primitives and
  * reference type values.
  *
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/LongSet.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/LongSet.kt
index 675d18e..09f75dc 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/LongSet.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/LongSet.kt
@@ -23,11 +23,14 @@
     "PrivatePropertyName",
     "NOTHING_TO_INLINE"
 )
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.annotation.IntRange
 import androidx.collection.internal.requirePrecondition
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
@@ -99,6 +102,38 @@
     MutableLongSet(elements.size).apply { plusAssign(elements) }
 
 /**
+ * Builds a new [LongSet] by populating a [MutableLongSet] using the given [builderAction].
+ *
+ * The set passed as a receiver to the [builderAction] is valid only inside that function. Using it
+ * outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableLongSet] can be populated.
+ */
+public inline fun buildLongSet(
+    builderAction: MutableLongSet.() -> Unit,
+): LongSet {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableLongSet().apply(builderAction)
+}
+
+/**
+ * Builds a new [LongSet] by populating a [MutableLongSet] using the given [builderAction].
+ *
+ * The set passed as a receiver to the [builderAction] is valid only inside that function. Using it
+ * outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableLongSet] can be populated.
+ */
+public inline fun buildLongSet(
+    initialCapacity: Int,
+    builderAction: MutableLongSet.() -> Unit,
+): LongSet {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableLongSet(initialCapacity).apply(builderAction)
+}
+
+/**
  * [LongSet] is a container with a [Set]-like interface designed to avoid allocations, including
  * boxing.
  *
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectFloatMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectFloatMap.kt
index 6c8b64f..7aa62ee 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectFloatMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectFloatMap.kt
@@ -15,12 +15,16 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.EMPTY_OBJECTS
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -217,6 +221,40 @@
     }
 
 /**
+ * Builds a new [ObjectFloatMap] by populating a [MutableObjectFloatMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableObjectFloatMap] can be populated.
+ */
+public inline fun <K> buildObjectFloatMap(
+    builderAction: MutableObjectFloatMap<K>.() -> Unit,
+): ObjectFloatMap<K> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableObjectFloatMap<K>().apply(builderAction)
+}
+
+/**
+ * Builds a new [ObjectFloatMap] by populating a [MutableObjectFloatMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableObjectFloatMap] can be populated.
+ */
+public inline fun <K> buildObjectFloatMap(
+    initialCapacity: Int,
+    builderAction: MutableObjectFloatMap<K>.() -> Unit,
+): ObjectFloatMap<K> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableObjectFloatMap<K>(initialCapacity).apply(builderAction)
+}
+
+/**
  * [ObjectFloatMap] is a container with a [Map]-like interface for keys with reference types and
  * [Float] primitives for values.
  *
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectIntMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectIntMap.kt
index ec7ec4b..6056fb6 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectIntMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectIntMap.kt
@@ -15,12 +15,16 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.EMPTY_OBJECTS
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -217,6 +221,40 @@
     }
 
 /**
+ * Builds a new [ObjectIntMap] by populating a [MutableObjectIntMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableObjectIntMap] can be populated.
+ */
+public inline fun <K> buildObjectIntMap(
+    builderAction: MutableObjectIntMap<K>.() -> Unit,
+): ObjectIntMap<K> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableObjectIntMap<K>().apply(builderAction)
+}
+
+/**
+ * Builds a new [ObjectIntMap] by populating a [MutableObjectIntMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableObjectIntMap] can be populated.
+ */
+public inline fun <K> buildObjectIntMap(
+    initialCapacity: Int,
+    builderAction: MutableObjectIntMap<K>.() -> Unit,
+): ObjectIntMap<K> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableObjectIntMap<K>(initialCapacity).apply(builderAction)
+}
+
+/**
  * [ObjectIntMap] is a container with a [Map]-like interface for keys with reference types and [Int]
  * primitives for values.
  *
diff --git a/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectLongMap.kt b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectLongMap.kt
index 3622689..f97dd66 100644
--- a/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectLongMap.kt
+++ b/collection/collection/src/commonMain/kotlin/androidx/collection/ObjectLongMap.kt
@@ -15,12 +15,16 @@
  */
 
 @file:Suppress("RedundantVisibilityModifier", "NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.EMPTY_OBJECTS
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -217,6 +221,40 @@
     }
 
 /**
+ * Builds a new [ObjectLongMap] by populating a [MutableObjectLongMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableObjectLongMap] can be populated.
+ */
+public inline fun <K> buildObjectLongMap(
+    builderAction: MutableObjectLongMap<K>.() -> Unit,
+): ObjectLongMap<K> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableObjectLongMap<K>().apply(builderAction)
+}
+
+/**
+ * Builds a new [ObjectLongMap] by populating a [MutableObjectLongMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableObjectLongMap] can be populated.
+ */
+public inline fun <K> buildObjectLongMap(
+    initialCapacity: Int,
+    builderAction: MutableObjectLongMap<K>.() -> Unit,
+): ObjectLongMap<K> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableObjectLongMap<K>(initialCapacity).apply(builderAction)
+}
+
+/**
  * [ObjectLongMap] is a container with a [Map]-like interface for keys with reference types and
  * [Long] primitives for values.
  *
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/DoubleListTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/DoubleListTest.kt
index 4c324c7..e3bde2e 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/DoubleListTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/DoubleListTest.kt
@@ -715,6 +715,36 @@
     }
 
     @Test
+    fun buildDoubleListFunction() {
+        val contract: Boolean
+        val l = buildDoubleList {
+            contract = true
+            add(2.0)
+            add(10.0)
+        }
+        assertTrue(contract)
+        assertEquals(2, l.size)
+        assertEquals(2.0, l[0])
+        assertEquals(10.0, l[1])
+    }
+
+    @Test
+    fun buildDoubleListWithCapacityFunction() {
+        val contract: Boolean
+        val l =
+            buildDoubleList(20) {
+                contract = true
+                add(2.0)
+                add(10.0)
+            }
+        assertTrue(contract)
+        assertEquals(2, l.size)
+        assertTrue(l.content.size >= 20)
+        assertEquals(2.0, l[0])
+        assertEquals(10.0, l[1])
+    }
+
+    @Test
     fun binarySearchDoubleList() {
         val l = mutableDoubleListOf(-2.0, -1.0, 2.0, 10.0, 10.0)
         assertEquals(0, l.binarySearch(-2))
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatFloatMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatFloatMapTest.kt
index c6b52ef..f8068cc 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatFloatMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatFloatMapTest.kt
@@ -228,6 +228,36 @@
     }
 
     @Test
+    fun buildFloatFloatMapFunction() {
+        val contract: Boolean
+        val map = buildFloatFloatMap {
+            contract = true
+            put(1f, 1f)
+            put(2f, 2f)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1f, map[1f])
+        assertEquals(2f, map[2f])
+    }
+
+    @Test
+    fun buildFloatObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildFloatFloatMap(20) {
+                contract = true
+                put(1f, 1f)
+                put(2f, 2f)
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1f, map[1f])
+        assertEquals(2f, map[2f])
+    }
+
+    @Test
     fun addToMap() {
         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 4d99c61..0e116a2 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatIntMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatIntMapTest.kt
@@ -228,6 +228,36 @@
     }
 
     @Test
+    fun buildFloatIntMapFunction() {
+        val contract: Boolean
+        val map = buildFloatIntMap {
+            contract = true
+            put(1f, 1)
+            put(2f, 2)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1, map[1f])
+        assertEquals(2, map[2f])
+    }
+
+    @Test
+    fun buildFloatObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildFloatIntMap(20) {
+                contract = true
+                put(1f, 1)
+                put(2f, 2)
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1, map[1f])
+        assertEquals(2, map[2f])
+    }
+
+    @Test
     fun addToMap() {
         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 e1b4649c..17b2a9d 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatListTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatListTest.kt
@@ -715,6 +715,36 @@
     }
 
     @Test
+    fun buildFloatListFunction() {
+        val contract: Boolean
+        val l = buildFloatList {
+            contract = true
+            add(2f)
+            add(10f)
+        }
+        assertTrue(contract)
+        assertEquals(2, l.size)
+        assertEquals(2f, l[0])
+        assertEquals(10f, l[1])
+    }
+
+    @Test
+    fun buildFloatListWithCapacityFunction() {
+        val contract: Boolean
+        val l =
+            buildFloatList(20) {
+                contract = true
+                add(2f)
+                add(10f)
+            }
+        assertTrue(contract)
+        assertEquals(2, l.size)
+        assertTrue(l.content.size >= 20)
+        assertEquals(2f, l[0])
+        assertEquals(10f, l[1])
+    }
+
+    @Test
     fun binarySearchFloatList() {
         val l = mutableFloatListOf(-2f, -1f, 2f, 10f, 10f)
         assertEquals(0, l.binarySearch(-2))
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatLongMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatLongMapTest.kt
index 6029c51..a307716 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatLongMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatLongMapTest.kt
@@ -228,6 +228,36 @@
     }
 
     @Test
+    fun buildFloatLongMapFunction() {
+        val contract: Boolean
+        val map = buildFloatLongMap {
+            contract = true
+            put(1f, 1L)
+            put(2f, 2L)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1L, map[1f])
+        assertEquals(2L, map[2f])
+    }
+
+    @Test
+    fun buildFloatObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildFloatLongMap(20) {
+                contract = true
+                put(1f, 1L)
+                put(2f, 2L)
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1L, map[1f])
+        assertEquals(2L, map[2f])
+    }
+
+    @Test
     fun addToMap() {
         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 572dd68..f0e20dc 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatObjectMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatObjectMapTest.kt
@@ -228,6 +228,36 @@
     }
 
     @Test
+    fun buildFloatObjectMapFunction() {
+        val contract: Boolean
+        val map = buildFloatObjectMap {
+            contract = true
+            put(1f, "World")
+            put(2f, "Monde")
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals("World", map[1f])
+        assertEquals("Monde", map[2f])
+    }
+
+    @Test
+    fun buildFloatObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildFloatObjectMap(20) {
+                contract = true
+                put(1f, "World")
+                put(2f, "Monde")
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals("World", map[1f])
+        assertEquals("Monde", map[2f])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutableFloatObjectMap<String>()
         map[1f] = "World"
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatSetTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatSetTest.kt
index e5a105e..70d38a5 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/FloatSetTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/FloatSetTest.kt
@@ -552,6 +552,36 @@
     }
 
     @Test
+    fun buildFloatSetFunction() {
+        val contract: Boolean
+        val set = buildFloatSet {
+            contract = true
+            add(1f)
+            add(2f)
+        }
+        assertTrue(contract)
+        assertEquals(2, set.size)
+        assertTrue(1f in set)
+        assertTrue(2f in set)
+    }
+
+    @Test
+    fun buildFloatSetWithCapacityFunction() {
+        val contract: Boolean
+        val set =
+            buildFloatSet(20) {
+                contract = true
+                add(1f)
+                add(2f)
+            }
+        assertTrue(contract)
+        assertEquals(2, set.size)
+        assertTrue(set.capacity >= 18)
+        assertTrue(1f in set)
+        assertTrue(2f in set)
+    }
+
+    @Test
     fun insertManyRemoveMany() {
         val set = mutableFloatSetOf()
 
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/IntFloatMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/IntFloatMapTest.kt
index cf9a934..eb3ea90 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/IntFloatMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntFloatMapTest.kt
@@ -228,6 +228,36 @@
     }
 
     @Test
+    fun buildIntFloatMapFunction() {
+        val contract: Boolean
+        val map = buildIntFloatMap {
+            contract = true
+            put(1, 1f)
+            put(2, 2f)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1f, map[1])
+        assertEquals(2f, map[2])
+    }
+
+    @Test
+    fun buildIntObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildIntFloatMap(20) {
+                contract = true
+                put(1, 1f)
+                put(2, 2f)
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1f, map[1])
+        assertEquals(2f, map[2])
+    }
+
+    @Test
     fun addToMap() {
         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 b7d318b..4a2691d 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/IntIntMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntIntMapTest.kt
@@ -228,6 +228,36 @@
     }
 
     @Test
+    fun buildIntIntMapFunction() {
+        val contract: Boolean
+        val map = buildIntIntMap {
+            contract = true
+            put(1, 1)
+            put(2, 2)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1, map[1])
+        assertEquals(2, map[2])
+    }
+
+    @Test
+    fun buildIntObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildIntIntMap(20) {
+                contract = true
+                put(1, 1)
+                put(2, 2)
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1, map[1])
+        assertEquals(2, map[2])
+    }
+
+    @Test
     fun addToMap() {
         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 cdc0cf9..6023ec2 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/IntListTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntListTest.kt
@@ -715,6 +715,36 @@
     }
 
     @Test
+    fun buildIntListFunction() {
+        val contract: Boolean
+        val l = buildIntList {
+            contract = true
+            add(2)
+            add(10)
+        }
+        assertTrue(contract)
+        assertEquals(2, l.size)
+        assertEquals(2, l[0])
+        assertEquals(10, l[1])
+    }
+
+    @Test
+    fun buildIntListWithCapacityFunction() {
+        val contract: Boolean
+        val l =
+            buildIntList(20) {
+                contract = true
+                add(2)
+                add(10)
+            }
+        assertTrue(contract)
+        assertEquals(2, l.size)
+        assertTrue(l.content.size >= 20)
+        assertEquals(2, l[0])
+        assertEquals(10, l[1])
+    }
+
+    @Test
     fun binarySearchIntList() {
         val l = mutableIntListOf(-2, -1, 2, 10, 10)
         assertEquals(0, l.binarySearch(-2))
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/IntLongMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/IntLongMapTest.kt
index e115dc6..7256aa0 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/IntLongMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntLongMapTest.kt
@@ -228,6 +228,36 @@
     }
 
     @Test
+    fun buildIntLongMapFunction() {
+        val contract: Boolean
+        val map = buildIntLongMap {
+            contract = true
+            put(1, 1L)
+            put(2, 2L)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1L, map[1])
+        assertEquals(2L, map[2])
+    }
+
+    @Test
+    fun buildIntObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildIntLongMap(20) {
+                contract = true
+                put(1, 1L)
+                put(2, 2L)
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1L, map[1])
+        assertEquals(2L, map[2])
+    }
+
+    @Test
     fun addToMap() {
         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 9d002e6..2193d75 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/IntObjectMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntObjectMapTest.kt
@@ -228,6 +228,36 @@
     }
 
     @Test
+    fun buildIntObjectMapFunction() {
+        val contract: Boolean
+        val map = buildIntObjectMap {
+            contract = true
+            put(1, "World")
+            put(2, "Monde")
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals("World", map[1])
+        assertEquals("Monde", map[2])
+    }
+
+    @Test
+    fun buildIntObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildIntObjectMap(20) {
+                contract = true
+                put(1, "World")
+                put(2, "Monde")
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals("World", map[1])
+        assertEquals("Monde", map[2])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutableIntObjectMap<String>()
         map[1] = "World"
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/IntSetTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/IntSetTest.kt
index 66c30d3..46a4c63 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/IntSetTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/IntSetTest.kt
@@ -552,6 +552,36 @@
     }
 
     @Test
+    fun buildIntSetFunction() {
+        val contract: Boolean
+        val set = buildIntSet {
+            contract = true
+            add(1)
+            add(2)
+        }
+        assertTrue(contract)
+        assertEquals(2, set.size)
+        assertTrue(1 in set)
+        assertTrue(2 in set)
+    }
+
+    @Test
+    fun buildIntSetWithCapacityFunction() {
+        val contract: Boolean
+        val set =
+            buildIntSet(20) {
+                contract = true
+                add(1)
+                add(2)
+            }
+        assertTrue(contract)
+        assertEquals(2, set.size)
+        assertTrue(set.capacity >= 18)
+        assertTrue(1 in set)
+        assertTrue(2 in set)
+    }
+
+    @Test
     fun insertManyRemoveMany() {
         val set = mutableIntSetOf()
 
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/LongFloatMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/LongFloatMapTest.kt
index 507ddf0..4201211 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/LongFloatMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongFloatMapTest.kt
@@ -228,6 +228,36 @@
     }
 
     @Test
+    fun buildLongFloatMapFunction() {
+        val contract: Boolean
+        val map = buildLongFloatMap {
+            contract = true
+            put(1L, 1f)
+            put(2L, 2f)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1f, map[1L])
+        assertEquals(2f, map[2L])
+    }
+
+    @Test
+    fun buildLongObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildLongFloatMap(20) {
+                contract = true
+                put(1L, 1f)
+                put(2L, 2f)
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1f, map[1L])
+        assertEquals(2f, map[2L])
+    }
+
+    @Test
     fun addToMap() {
         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 75488a2..568fc7a 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/LongIntMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongIntMapTest.kt
@@ -228,6 +228,36 @@
     }
 
     @Test
+    fun buildLongIntMapFunction() {
+        val contract: Boolean
+        val map = buildLongIntMap {
+            contract = true
+            put(1L, 1)
+            put(2L, 2)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1, map[1L])
+        assertEquals(2, map[2L])
+    }
+
+    @Test
+    fun buildLongObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildLongIntMap(20) {
+                contract = true
+                put(1L, 1)
+                put(2L, 2)
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1, map[1L])
+        assertEquals(2, map[2L])
+    }
+
+    @Test
     fun addToMap() {
         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 af0536e..64019c1 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/LongListTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongListTest.kt
@@ -715,6 +715,36 @@
     }
 
     @Test
+    fun buildLongListFunction() {
+        val contract: Boolean
+        val l = buildLongList {
+            contract = true
+            add(2L)
+            add(10L)
+        }
+        assertTrue(contract)
+        assertEquals(2, l.size)
+        assertEquals(2L, l[0])
+        assertEquals(10L, l[1])
+    }
+
+    @Test
+    fun buildLongListWithCapacityFunction() {
+        val contract: Boolean
+        val l =
+            buildLongList(20) {
+                contract = true
+                add(2L)
+                add(10L)
+            }
+        assertTrue(contract)
+        assertEquals(2, l.size)
+        assertTrue(l.content.size >= 20)
+        assertEquals(2L, l[0])
+        assertEquals(10L, l[1])
+    }
+
+    @Test
     fun binarySearchLongList() {
         val l = mutableLongListOf(-2L, -1L, 2L, 10L, 10L)
         assertEquals(0, l.binarySearch(-2))
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/LongLongMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/LongLongMapTest.kt
index a22631b..6af2158 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/LongLongMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongLongMapTest.kt
@@ -228,6 +228,36 @@
     }
 
     @Test
+    fun buildLongLongMapFunction() {
+        val contract: Boolean
+        val map = buildLongLongMap {
+            contract = true
+            put(1L, 1L)
+            put(2L, 2L)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1L, map[1L])
+        assertEquals(2L, map[2L])
+    }
+
+    @Test
+    fun buildLongObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildLongLongMap(20) {
+                contract = true
+                put(1L, 1L)
+                put(2L, 2L)
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1L, map[1L])
+        assertEquals(2L, map[2L])
+    }
+
+    @Test
     fun addToMap() {
         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 f8be517..3b6747d 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/LongObjectMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongObjectMapTest.kt
@@ -228,6 +228,36 @@
     }
 
     @Test
+    fun buildLongObjectMapFunction() {
+        val contract: Boolean
+        val map = buildLongObjectMap {
+            contract = true
+            put(1L, "World")
+            put(2L, "Monde")
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals("World", map[1L])
+        assertEquals("Monde", map[2L])
+    }
+
+    @Test
+    fun buildLongObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildLongObjectMap(20) {
+                contract = true
+                put(1L, "World")
+                put(2L, "Monde")
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals("World", map[1L])
+        assertEquals("Monde", map[2L])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutableLongObjectMap<String>()
         map[1L] = "World"
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/LongSetTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/LongSetTest.kt
index dc5b7e7..577e722e 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/LongSetTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/LongSetTest.kt
@@ -552,6 +552,36 @@
     }
 
     @Test
+    fun buildLongSetFunction() {
+        val contract: Boolean
+        val set = buildLongSet {
+            contract = true
+            add(1L)
+            add(2L)
+        }
+        assertTrue(contract)
+        assertEquals(2, set.size)
+        assertTrue(1L in set)
+        assertTrue(2L in set)
+    }
+
+    @Test
+    fun buildLongSetWithCapacityFunction() {
+        val contract: Boolean
+        val set =
+            buildLongSet(20) {
+                contract = true
+                add(1L)
+                add(2L)
+            }
+        assertTrue(contract)
+        assertEquals(2, set.size)
+        assertTrue(set.capacity >= 18)
+        assertTrue(1L in set)
+        assertTrue(2L in set)
+    }
+
+    @Test
     fun insertManyRemoveMany() {
         val set = mutableLongSetOf()
 
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectFloatMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectFloatMapTest.kt
index 90244ab..19c1cc6 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectFloatMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectFloatMapTest.kt
@@ -229,6 +229,36 @@
     }
 
     @Test
+    fun buildObjectFloatMapFunction() {
+        val contract: Boolean
+        val map = buildObjectFloatMap {
+            contract = true
+            put("Hello", 1f)
+            put("Bonjour", 2f)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1f, map["Hello"])
+        assertEquals(2f, map["Bonjour"])
+    }
+
+    @Test
+    fun buildObjectFloatMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildObjectFloatMap(20) {
+                contract = true
+                put("Hello", 1f)
+                put("Bonjour", 2f)
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1f, map["Hello"])
+        assertEquals(2f, map["Bonjour"])
+    }
+
+    @Test
     fun addToMap() {
         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 bc640eb..62b7a2c 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectIntMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectIntMapTest.kt
@@ -229,6 +229,36 @@
     }
 
     @Test
+    fun buildObjectIntMapFunction() {
+        val contract: Boolean
+        val map = buildObjectIntMap {
+            contract = true
+            put("Hello", 1)
+            put("Bonjour", 2)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1, map["Hello"])
+        assertEquals(2, map["Bonjour"])
+    }
+
+    @Test
+    fun buildObjectIntMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildObjectIntMap(20) {
+                contract = true
+                put("Hello", 1)
+                put("Bonjour", 2)
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1, map["Hello"])
+        assertEquals(2, map["Bonjour"])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutableObjectIntMap<String>()
         map["Hello"] = 1
diff --git a/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectLongMapTest.kt b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectLongMapTest.kt
index f728b95..6eba1a6 100644
--- a/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectLongMapTest.kt
+++ b/collection/collection/src/commonTest/kotlin/androidx/collection/ObjectLongMapTest.kt
@@ -229,6 +229,36 @@
     }
 
     @Test
+    fun buildObjectLongMapFunction() {
+        val contract: Boolean
+        val map = buildObjectLongMap {
+            contract = true
+            put("Hello", 1L)
+            put("Bonjour", 2L)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1L, map["Hello"])
+        assertEquals(2L, map["Bonjour"])
+    }
+
+    @Test
+    fun buildObjectLongMapWithCapacityFunction() {
+        val contract: Boolean
+        val map =
+            buildObjectLongMap(20) {
+                contract = true
+                put("Hello", 1L)
+                put("Bonjour", 2L)
+            }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1L, map["Hello"])
+        assertEquals(2L, map["Bonjour"])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutableObjectLongMap<String>()
         map["Hello"] = 1L
diff --git a/collection/collection/template/ObjectPValueMap.kt.template b/collection/collection/template/ObjectPValueMap.kt.template
index bfc791c..9980351 100644
--- a/collection/collection/template/ObjectPValueMap.kt.template
+++ b/collection/collection/template/ObjectPValueMap.kt.template
@@ -18,12 +18,16 @@
     "RedundantVisibilityModifier",
     "NOTHING_TO_INLINE"
 )
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.EMPTY_OBJECTS
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -240,6 +244,40 @@
     }
 
 /**
+ * Builds a new [ObjectPValueMap] by populating a [MutableObjectPValueMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableObjectPValueMap] can be populated.
+ */
+public inline fun <K> buildObjectPValueMap(
+    builderAction: MutableObjectPValueMap<K>.() -> Unit,
+): ObjectPValueMap<K> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableObjectPValueMap<K>().apply(builderAction)
+}
+
+/**
+ * Builds a new [ObjectPValueMap] by populating a [MutableObjectPValueMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableObjectPValueMap] can be populated.
+ */
+public inline fun <K> buildObjectPValueMap(
+    initialCapacity: Int,
+    builderAction: MutableObjectPValueMap<K>.() -> Unit,
+): ObjectPValueMap<K> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableObjectPValueMap<K>(initialCapacity).apply(builderAction)
+}
+
+/**
  * [ObjectPValueMap] is a container with a [Map]-like interface for keys with
  * reference types and [PValue] primitives for values.
  *
diff --git a/collection/collection/template/ObjectPValueMapTest.kt.template b/collection/collection/template/ObjectPValueMapTest.kt.template
index 988abb3..c84db2d 100644
--- a/collection/collection/template/ObjectPValueMapTest.kt.template
+++ b/collection/collection/template/ObjectPValueMapTest.kt.template
@@ -189,6 +189,35 @@
     }
 
     @Test
+    fun buildObjectPValueMapFunction() {
+        val contract: Boolean
+        val map = buildObjectPValueMap {
+            contract = true
+            put("Hello", 1ValueSuffix)
+            put("Bonjour", 2ValueSuffix)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1ValueSuffix, map["Hello"])
+        assertEquals(2ValueSuffix, map["Bonjour"])
+    }
+
+    @Test
+    fun buildObjectPValueMapWithCapacityFunction() {
+        val contract: Boolean
+        val map = buildObjectPValueMap(20) {
+            contract = true
+            put("Hello", 1ValueSuffix)
+            put("Bonjour", 2ValueSuffix)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1ValueSuffix, map["Hello"])
+        assertEquals(2ValueSuffix, map["Bonjour"])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutableObjectPValueMap<String>()
         map["Hello"] = 1ValueSuffix
diff --git a/collection/collection/template/PKeyList.kt.template b/collection/collection/template/PKeyList.kt.template
index 03233b7..61a2ff2 100644
--- a/collection/collection/template/PKeyList.kt.template
+++ b/collection/collection/template/PKeyList.kt.template
@@ -23,6 +23,7 @@
 import androidx.collection.internal.throwIndexOutOfBoundsException
 import androidx.collection.internal.throwNoSuchElementException
 import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
@@ -1012,3 +1013,37 @@
  */
 public inline fun mutablePKeyListOf(vararg elements: PKey): MutablePKeyList =
     MutablePKeyList(elements.size).apply { plusAssign(elements) }
+
+/**
+ * Builds a new [PKeyList] by populating a [MutablePKeyList] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutablePKeyList] can be populated.
+ */
+public inline fun buildPKeyList(
+    builderAction: MutablePKeyList.() -> Unit,
+): PKeyList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutablePKeyList().apply(builderAction)
+}
+
+/**
+ * Builds a new [PKeyList] by populating a [MutablePKeyList] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ * @param builderAction Lambda in which the [MutablePKeyList] can be populated.
+ */
+public inline fun buildPKeyList(
+    initialCapacity: Int,
+    builderAction: MutablePKeyList.() -> Unit,
+): PKeyList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutablePKeyList(initialCapacity).apply(builderAction)
+}
diff --git a/collection/collection/template/PKeyListTest.kt.template b/collection/collection/template/PKeyListTest.kt.template
index 8f3d8e0..417c489 100644
--- a/collection/collection/template/PKeyListTest.kt.template
+++ b/collection/collection/template/PKeyListTest.kt.template
@@ -751,6 +751,35 @@
     }
 
     @Test
+    fun buildPKeyListFunction() {
+        val contract: Boolean
+        val l = buildPKeyList {
+            contract = true
+            add(2KeySuffix)
+            add(10KeySuffix)
+        }
+        assertTrue(contract)
+        assertEquals(2, l.size)
+        assertEquals(2KeySuffix, l[0])
+        assertEquals(10KeySuffix, l[1])
+    }
+
+    @Test
+    fun buildPKeyListWithCapacityFunction() {
+        val contract: Boolean
+        val l = buildPKeyList(20) {
+            contract = true
+            add(2KeySuffix)
+            add(10KeySuffix)
+        }
+        assertTrue(contract)
+        assertEquals(2, l.size)
+        assertTrue(l.content.size >= 20)
+        assertEquals(2KeySuffix, l[0])
+        assertEquals(10KeySuffix, l[1])
+    }
+
+    @Test
     fun binarySearchPKeyList() {
         val l = mutablePKeyListOf(-2KeySuffix, -1KeySuffix, 2KeySuffix, 10KeySuffix, 10KeySuffix)
         assertEquals(0, l.binarySearch(-2))
diff --git a/collection/collection/template/PKeyObjectMap.kt.template b/collection/collection/template/PKeyObjectMap.kt.template
index 39bb237..601eb4e 100644
--- a/collection/collection/template/PKeyObjectMap.kt.template
+++ b/collection/collection/template/PKeyObjectMap.kt.template
@@ -18,11 +18,15 @@
     "RedundantVisibilityModifier",
     "NOTHING_TO_INLINE"
 )
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.EMPTY_OBJECTS
 import androidx.collection.internal.requirePrecondition
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -227,6 +231,40 @@
     }
 
 /**
+ * Builds a new [PKeyObjectMap] by populating a [MutablePKeyObjectMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutablePKeyObjectMap] can be populated.
+ */
+public inline fun <V> buildPKeyObjectMap(
+    builderAction: MutablePKeyObjectMap<V>.() -> Unit,
+): PKeyObjectMap<V> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutablePKeyObjectMap<V>().apply(builderAction)
+}
+
+/**
+ * Builds a new [PKeyObjectMap] by populating a [MutablePKeyObjectMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutablePKeyObjectMap] can be populated.
+ */
+public inline fun <V> buildPKeyObjectMap(
+    initialCapacity: Int,
+    builderAction: MutablePKeyObjectMap<V>.() -> Unit,
+): PKeyObjectMap<V> {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutablePKeyObjectMap<V>(initialCapacity).apply(builderAction)
+}
+
+/**
  * [PKeyObjectMap] is a container with a [Map]-like interface for keys with
  * [PKey] primitives and reference type values.
  *
diff --git a/collection/collection/template/PKeyObjectMapTest.kt.template b/collection/collection/template/PKeyObjectMapTest.kt.template
index ea5cfb8..de9c4ff 100644
--- a/collection/collection/template/PKeyObjectMapTest.kt.template
+++ b/collection/collection/template/PKeyObjectMapTest.kt.template
@@ -188,6 +188,35 @@
     }
 
     @Test
+    fun buildPKeyObjectMapFunction() {
+        val contract: Boolean
+        val map = buildPKeyObjectMap {
+            contract = true
+            put(1KeySuffix, "World")
+            put(2KeySuffix, "Monde")
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals("World", map[1KeySuffix])
+        assertEquals("Monde", map[2KeySuffix])
+    }
+
+    @Test
+    fun buildPKeyObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map = buildPKeyObjectMap(20) {
+            contract = true
+            put(1KeySuffix, "World")
+            put(2KeySuffix, "Monde")
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals("World", map[1KeySuffix])
+        assertEquals("Monde", map[2KeySuffix])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutablePKeyObjectMap<String>()
         map[1KeySuffix] = "World"
diff --git a/collection/collection/template/PKeyPValueMap.kt.template b/collection/collection/template/PKeyPValueMap.kt.template
index 524dbe7..53d0eb3 100644
--- a/collection/collection/template/PKeyPValueMap.kt.template
+++ b/collection/collection/template/PKeyPValueMap.kt.template
@@ -18,11 +18,15 @@
     "RedundantVisibilityModifier",
     "NOTHING_TO_INLINE"
 )
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
 
@@ -225,6 +229,40 @@
     }
 
 /**
+ * Builds a new [PKeyPValueMap] by populating a [MutablePKeyPValueMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutablePKeyPValueMap] can be populated.
+ */
+public inline fun buildPKeyPValueMap(
+    builderAction: MutablePKeyPValueMap.() -> Unit,
+): PKeyPValueMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutablePKeyPValueMap().apply(builderAction)
+}
+
+/**
+ * Builds a new [PKeyPValueMap] by populating a [MutablePKeyPValueMap] using the given
+ * [builderAction].
+ *
+ * The instance passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of pairs added in the [builderAction].
+ * @param builderAction Lambda in which the [MutablePKeyPValueMap] can be populated.
+ */
+public inline fun buildPKeyPValueMap(
+    initialCapacity: Int,
+    builderAction: MutablePKeyPValueMap.() -> Unit,
+): PKeyPValueMap {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutablePKeyPValueMap(initialCapacity).apply(builderAction)
+}
+
+/**
  * [PKeyPValueMap] is a container with a [Map]-like interface for
  * [PKey] primitive keys and [PValue] primitive values.
  *
diff --git a/collection/collection/template/PKeyPValueMapTest.kt.template b/collection/collection/template/PKeyPValueMapTest.kt.template
index a61070f..a190dae 100644
--- a/collection/collection/template/PKeyPValueMapTest.kt.template
+++ b/collection/collection/template/PKeyPValueMapTest.kt.template
@@ -188,6 +188,35 @@
     }
 
     @Test
+    fun buildPKeyPValueMapFunction() {
+        val contract: Boolean
+        val map = buildPKeyPValueMap {
+            contract = true
+            put(1KeySuffix, 1ValueSuffix)
+            put(2KeySuffix, 2ValueSuffix)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertEquals(1ValueSuffix, map[1KeySuffix])
+        assertEquals(2ValueSuffix, map[2KeySuffix])
+    }
+
+    @Test
+    fun buildPKeyObjectMapWithCapacityFunction() {
+        val contract: Boolean
+        val map = buildPKeyPValueMap(20) {
+            contract = true
+            put(1KeySuffix, 1ValueSuffix)
+            put(2KeySuffix, 2ValueSuffix)
+        }
+        assertTrue(contract)
+        assertEquals(2, map.size)
+        assertTrue(map.capacity >= 18)
+        assertEquals(1ValueSuffix, map[1KeySuffix])
+        assertEquals(2ValueSuffix, map[2KeySuffix])
+    }
+
+    @Test
     fun addToMap() {
         val map = MutablePKeyPValueMap()
         map[1KeySuffix] = 1ValueSuffix
diff --git a/collection/collection/template/PKeySet.kt.template b/collection/collection/template/PKeySet.kt.template
index 38e36a4..a33c487 100644
--- a/collection/collection/template/PKeySet.kt.template
+++ b/collection/collection/template/PKeySet.kt.template
@@ -23,12 +23,15 @@
     "PrivatePropertyName",
     "NOTHING_TO_INLINE"
 )
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.collection
 
 import androidx.annotation.IntRange
 import androidx.collection.internal.requirePrecondition
 import androidx.collection.internal.throwNoSuchElementException
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
@@ -126,6 +129,40 @@
     MutablePKeySet(elements.size).apply { plusAssign(elements) }
 
 /**
+ * Builds a new [PKeySet] by populating a [MutablePKeySet] using the given
+ * [builderAction].
+ *
+ * The set passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutablePKeySet] can be populated.
+ */
+public inline fun buildPKeySet(
+    builderAction: MutablePKeySet.() -> Unit,
+): PKeySet {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutablePKeySet().apply(builderAction)
+}
+
+/**
+ * Builds a new [PKeySet] by populating a [MutablePKeySet] using the given
+ * [builderAction].
+ *
+ * The set passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ * @param builderAction Lambda in which the [MutablePKeySet] can be populated.
+ */
+public inline fun buildPKeySet(
+    initialCapacity: Int,
+    builderAction: MutablePKeySet.() -> Unit,
+): PKeySet {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutablePKeySet(initialCapacity).apply(builderAction)
+}
+
+/**
  * [PKeySet] is a container with a [Set]-like interface designed to avoid
  * allocations, including boxing.
  *
diff --git a/collection/collection/template/PKeySetTest.kt.template b/collection/collection/template/PKeySetTest.kt.template
index f9b12d5..05813d0 100644
--- a/collection/collection/template/PKeySetTest.kt.template
+++ b/collection/collection/template/PKeySetTest.kt.template
@@ -562,6 +562,35 @@
     }
 
     @Test
+    fun buildPKeySetFunction() {
+        val contract: Boolean
+        val set = buildPKeySet {
+            contract = true
+            add(1KeySuffix)
+            add(2KeySuffix)
+        }
+        assertTrue(contract)
+        assertEquals(2, set.size)
+        assertTrue(1KeySuffix in set)
+        assertTrue(2KeySuffix in set)
+    }
+
+    @Test
+    fun buildPKeySetWithCapacityFunction() {
+        val contract: Boolean
+        val set = buildPKeySet(20) {
+            contract = true
+            add(1KeySuffix)
+            add(2KeySuffix)
+        }
+        assertTrue(contract)
+        assertEquals(2, set.size)
+        assertTrue(set.capacity >= 18)
+        assertTrue(1KeySuffix in set)
+        assertTrue(2KeySuffix in set)
+    }
+
+    @Test
     fun insertManyRemoveMany() {
         val set = mutablePKeySetOf()
 
diff --git a/collection/collection/template/ValueClassList.kt.template b/collection/collection/template/ValueClassList.kt.template
index 4ce50e6..88fc2dc 100644
--- a/collection/collection/template/ValueClassList.kt.template
+++ b/collection/collection/template/ValueClassList.kt.template
@@ -24,6 +24,7 @@
     "NOTHING_TO_INLINE",
     "UnusedImport",
 )
+@file:OptIn(ExperimentalContracts::class)
 
 package PACKAGE
 
@@ -34,6 +35,7 @@
 import androidx.collection.mutablePRIMITIVEListOf
 import VALUE_PKG.VALUE_CLASS
 import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmInline
 
@@ -57,7 +59,6 @@
  * the appropriate synchronization. It is also not safe to mutate during reentrancy --
  * in the middle of a [forEach], for example. However, concurrent reads are safe.
  */
-@OptIn(ExperimentalContracts::class)
 @JvmInline
 VISIBILITY value class VALUE_CLASSList(val list: PRIMITIVEList) {
     /**
@@ -377,7 +378,6 @@
  *
  * @constructor Creates a [MutableVALUE_CLASSList] with a [capacity] of `initialCapacity`.
  */
-@OptIn(ExperimentalContracts::class)
 @JvmInline
 VISIBILITY value class MutableVALUE_CLASSList(val list: MutablePRIMITIVEList) {
     public constructor(initialCapacity: Int = 16) : this(MutablePRIMITIVEList(initialCapacity))
@@ -934,3 +934,37 @@
         element3.BACKING_PROPERTY
     )
 )
+
+/**
+ * Builds a new [VALUE_CLASSList] by populating a [MutableVALUE_CLASSList] using the given
+ * [builderAction].
+ *
+ * The list passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableVALUE_CLASSList] can be populated.
+ */
+VISIBILITY inline fun buildVALUE_CLASSList(
+    builderAction: MutableVALUE_CLASSList.() -> Unit,
+): VALUE_CLASSList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableVALUE_CLASSList().apply(builderAction).asVALUE_CLASSList()
+}
+
+/**
+ * Builds a new [VALUE_CLASSList] by populating a [MutableVALUE_CLASSList] using the given
+ * [builderAction].
+ *
+ * The list passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableVALUE_CLASSList] can be populated.
+ */
+VISIBILITY inline fun buildVALUE_CLASSList(
+    initialCapacity: Int,
+    builderAction: MutableVALUE_CLASSList.() -> Unit,
+): VALUE_CLASSList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableVALUE_CLASSList(initialCapacity).apply(builderAction).asVALUE_CLASSList()
+}
diff --git a/collection/collection/template/ValueClassSet.kt.template b/collection/collection/template/ValueClassSet.kt.template
index de43cdf..cd2f51f 100644
--- a/collection/collection/template/ValueClassSet.kt.template
+++ b/collection/collection/template/ValueClassSet.kt.template
@@ -24,6 +24,7 @@
     "NOTHING_TO_INLINE",
     "UnusedImport",
 )
+@file:OptIn(ExperimentalContracts::class)
 
 package PACKAGE
 
@@ -34,6 +35,7 @@
 import androidx.collection.mutablePRIMITIVESetOf
 import VALUE_PKG.VALUE_CLASS
 import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmInline
 
@@ -137,6 +139,37 @@
     )
 
 /**
+ * Builds a new [VALUE_CLASSSet] by populating a [MutableVALUE_CLASSSet] using the given
+ * [builderAction].
+ *
+ * The set passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ */
+VISIBILITY inline fun buildVALUE_CLASSSet(
+    builderAction: MutableVALUE_CLASSSet.() -> Unit,
+): VALUE_CLASSSet {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableVALUE_CLASSSet().apply(builderAction).asVALUE_CLASSSet()
+}
+
+/**
+ * Builds a new [VALUE_CLASSSet] by populating a [MutableVALUE_CLASSSet] using the given
+ * [builderAction].
+ *
+ * The set passed as a receiver to the [builderAction] is valid only inside that function.
+ * Using it outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ */
+VISIBILITY inline fun buildVALUE_CLASSSet(
+    initialCapacity: Int,
+    builderAction: MutableVALUE_CLASSSet.() -> Unit,
+): VALUE_CLASSSet {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableVALUE_CLASSSet(initialCapacity).apply(builderAction).asVALUE_CLASSSet()
+}
+
+/**
  * [VALUE_CLASSSet] is a container with a [Set]-like interface designed to avoid
  * allocations, including boxing.
  *
@@ -151,7 +184,6 @@
  *
  * @see [MutableVALUE_CLASSSet]
  */
-@OptIn(ExperimentalContracts::class)
 @JvmInline
 VISIBILITY value class VALUE_CLASSSet(val set: PRIMITIVESet) {
     /**
@@ -302,7 +334,6 @@
  * the set (insertion or removal for instance), the calling code must provide
  * the appropriate synchronization. Concurrent reads are however safe.
  */
-@OptIn(ExperimentalContracts::class)
 @JvmInline
 VISIBILITY value class MutableVALUE_CLASSSet(val set: MutablePRIMITIVESet) {
     /**
diff --git a/compose/animation/animation-core-lint/src/main/java/androidx/compose/animation/core/lint/AnimationCoreIssueRegistry.kt b/compose/animation/animation-core-lint/src/main/java/androidx/compose/animation/core/lint/AnimationCoreIssueRegistry.kt
index 98ce6b0..afa9d37 100644
--- a/compose/animation/animation-core-lint/src/main/java/androidx/compose/animation/core/lint/AnimationCoreIssueRegistry.kt
+++ b/compose/animation/animation-core-lint/src/main/java/androidx/compose/animation/core/lint/AnimationCoreIssueRegistry.kt
@@ -23,7 +23,7 @@
 /** [IssueRegistry] containing animation-core specific lint issues. */
 class AnimationCoreIssueRegistry : IssueRegistry() {
     // Tests are run with this version. We ensure that with ApiLintVersionsTest
-    override val api = 14
+    override val api = 16
     override val minApi = CURRENT_API
     override val issues
         get() =
diff --git a/compose/animation/animation-core/build.gradle b/compose/animation/animation-core/build.gradle
index a0f829e..62e0b08 100644
--- a/compose/animation/animation-core/build.gradle
+++ b/compose/animation/animation-core/build.gradle
@@ -22,6 +22,8 @@
  * modifying its settings.
  */
 
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import androidx.build.PlatformIdentifier
 
@@ -127,6 +129,7 @@
     description = "Animation engine and animation primitives that are the building blocks of the Compose animation library"
     metalavaK2UastEnabled = false
     samples(project(":compose:animation:animation-core:animation-core-samples"))
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/animation/animation-core/lint-baseline.xml b/compose/animation/animation-core/lint-baseline.xml
index 8052a36..b2a82a7 100644
--- a/compose/animation/animation-core/lint-baseline.xml
+++ b/compose/animation/animation-core/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.7.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.7.0-alpha02)" variant="all" version="8.7.0-alpha02">
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
 
     <issue
         id="PrimitiveInCollection"
diff --git a/compose/animation/animation-core/samples/build.gradle b/compose/animation/animation-core/samples/build.gradle
index d6e8a00..db95848 100644
--- a/compose/animation/animation-core/samples/build.gradle
+++ b/compose/animation/animation-core/samples/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 
 plugins {
@@ -49,6 +51,7 @@
     type = LibraryType.SAMPLES
     inceptionYear = "2019"
     description = "Contains the sample code for the Androidx Compose UI Animation Core Classes"
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/animation/animation-graphics/build.gradle b/compose/animation/animation-graphics/build.gradle
index 16f1350..778cd29 100644
--- a/compose/animation/animation-graphics/build.gradle
+++ b/compose/animation/animation-graphics/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import androidx.build.PlatformIdentifier
 
@@ -118,6 +120,7 @@
     description = "Compose Animation Graphics Library for using animated-vector resources in Compose"
     metalavaK2UastEnabled = false
     samples(project(":compose:animation:animation-graphics:animation-graphics-samples"))
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/animation/animation-graphics/samples/build.gradle b/compose/animation/animation-graphics/samples/build.gradle
index dd05f1e..eac57cb 100644
--- a/compose/animation/animation-graphics/samples/build.gradle
+++ b/compose/animation/animation-graphics/samples/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 
 plugins {
@@ -49,6 +51,7 @@
     type = LibraryType.SAMPLES
     inceptionYear = "2021"
     description = "Contains the sample code for the Androidx Compose UI Animation Graphics Library"
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/animation/animation-lint/src/main/java/androidx/compose/animation/lint/AnimationIssueRegistry.kt b/compose/animation/animation-lint/src/main/java/androidx/compose/animation/lint/AnimationIssueRegistry.kt
index 7862a5c..8e6b263 100644
--- a/compose/animation/animation-lint/src/main/java/androidx/compose/animation/lint/AnimationIssueRegistry.kt
+++ b/compose/animation/animation-lint/src/main/java/androidx/compose/animation/lint/AnimationIssueRegistry.kt
@@ -25,7 +25,7 @@
 /** [IssueRegistry] containing animation specific lint issues. */
 class AnimationIssueRegistry : IssueRegistry() {
     // Tests are run with this version. We ensure that with ApiLintVersionsTest
-    override val api = 14
+    override val api = 16
     override val minApi = CURRENT_API
     override val issues
         get() =
diff --git a/compose/animation/animation/build.gradle b/compose/animation/animation/build.gradle
index c7595d2..fdc6664 100644
--- a/compose/animation/animation/build.gradle
+++ b/compose/animation/animation/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import androidx.build.PlatformIdentifier
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
@@ -125,6 +127,7 @@
     description = "Compose animation library"
     metalavaK2UastEnabled = false
     samples(project(":compose:animation:animation:animation-samples"))
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/animation/animation/integration-tests/animation-demos/lint-baseline.xml b/compose/animation/animation/integration-tests/animation-demos/lint-baseline.xml
index 866056a..d1285a8 100644
--- a/compose/animation/animation/integration-tests/animation-demos/lint-baseline.xml
+++ b/compose/animation/animation/integration-tests/animation-demos/lint-baseline.xml
@@ -1,14 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.7.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.7.0-alpha02)" variant="all" version="8.7.0-alpha02">
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="            anchors.removeLastKt()"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/animation/demos/suspendfun/OffsetKeyframeSplinePlaygroundDemo.kt"/>
-    </issue>
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
 
     <issue
         id="PrimitiveInCollection"
diff --git a/compose/animation/animation/samples/build.gradle b/compose/animation/animation/samples/build.gradle
index 66c33e58..68f3b69 100644
--- a/compose/animation/animation/samples/build.gradle
+++ b/compose/animation/animation/samples/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 
 plugins {
@@ -48,6 +50,7 @@
     type = LibraryType.SAMPLES
     inceptionYear = "2019"
     description = "Contains the sample code for the Androidx Compose UI Animation Library"
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt b/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
index 1bb80e5..5611700 100644
--- a/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
+++ b/compose/animation/animation/src/androidInstrumentedTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
@@ -18,6 +18,7 @@
 
 package androidx.compose.animation
 
+import android.annotation.SuppressLint
 import androidx.compose.animation.core.InternalAnimationApi
 import androidx.compose.animation.core.LinearEasing
 import androidx.compose.animation.core.MutableTransitionState
@@ -28,6 +29,7 @@
 import androidx.compose.animation.core.tween
 import androidx.compose.animation.core.updateTransition
 import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
@@ -63,6 +65,7 @@
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
@@ -93,7 +96,6 @@
     @get:Rule
     val ruleChain: RuleChain = RuleChain.outerRule(DetectLeaksAfterTestSuccess()).around(rule)
 
-    @OptIn(InternalAnimationApi::class)
     @Test
     fun AnimatedContentSizeTransformTest() {
         val size1 = 40
@@ -990,6 +992,76 @@
         rule.waitForIdle()
     }
 
+    @OptIn(ExperimentalSharedTransitionApi::class)
+    @SuppressLint("UnusedContentLambdaTargetStateParameter")
+    @Test
+    fun testSizeTransformAlwaysContinuous() {
+        var large by mutableStateOf(false)
+        var currentWidth: Int = 0
+        var currentHeight: Int = 0
+        rule.setContent {
+            CompositionLocalProvider(LocalDensity provides Density(1f)) {
+                LookaheadScope {
+                    Box(Modifier.clickable { large = !large }) {
+                        AnimatedContent(
+                            label = "Test",
+                            modifier =
+                                Modifier.animateBounds(
+                                        this@LookaheadScope,
+                                        if (!large) Modifier.size(200.dp) else Modifier.size(300.dp)
+                                    )
+                                    .layout { m, c ->
+                                        m.measure(
+                                                c.copy(
+                                                    maxWidth = Constraints.Infinity,
+                                                    maxHeight = Constraints.Infinity
+                                                )
+                                            )
+                                            .run {
+                                                if (!isLookingAhead) {
+                                                    currentWidth = width
+                                                    currentHeight = height
+                                                }
+                                                layout(width, height) { place(0, 0) }
+                                            }
+                                    }
+                                    .background(Color.Gray),
+                            targetState = large,
+                            contentKey = { true },
+                            transitionSpec = { fadeIn().togetherWith(fadeOut()) }
+                        ) {
+                            Box(Modifier.background(Color.Black).size(200.dp, 100.dp))
+                        }
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+        rule.mainClock.autoAdvance = false
+        large = true
+
+        assertEquals(200, currentWidth)
+        assertEquals(200, currentHeight)
+        // Expect size to grow
+        var lastWidth: Int = 200
+        var lastHeight: Int = 200
+
+        fun doFrame() {
+            rule.mainClock.advanceTimeByFrame()
+            rule.waitForIdle()
+            assertTrue(currentWidth >= lastWidth)
+            assertTrue(currentHeight >= lastHeight)
+            lastWidth = currentWidth
+            lastHeight = currentHeight
+        }
+
+        repeat(5) { doFrame() }
+
+        while (currentWidth != 300 || currentHeight != 300) {
+            doFrame()
+        }
+    }
+
     @Test
     fun testTargetChangeLookaheadPlacement() {
         var lookaheadPosition1: Offset? = null
@@ -1087,7 +1159,6 @@
         assertEquals(expected.y, actual.y, 0.00001f)
     }
 
-    @OptIn(InternalAnimationApi::class)
     private val Transition<*>.playTimeMillis
         get() = (playTimeNanos / 1_000_000L).toInt()
 }
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt
index f095141..da19920 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt
@@ -60,6 +60,8 @@
 import androidx.compose.ui.layout.ParentDataModifier
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.layout.layout
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
@@ -563,16 +565,23 @@
                 shouldAnimateSize = true
             }
         }
+        val sizeAnimation: Transition<S>.DeferredAnimation<IntSize, AnimationVector2D>?
         return if (shouldAnimateSize) {
-            val sizeAnimation = transition.createDeferredAnimation(IntSize.VectorConverter)
-            remember(sizeAnimation) {
-                (if (sizeTransform.value?.clip == false) Modifier else Modifier.clipToBounds())
-                    .then(SizeModifier(sizeAnimation, sizeTransform))
+                sizeAnimation = transition.createDeferredAnimation(IntSize.VectorConverter)
+                remember(sizeAnimation) {
+                    (if (sizeTransform.value?.clip == false) Modifier else Modifier.clipToBounds())
+                }
+            } else {
+                sizeAnimation = null
+                animatedSize = null
+                Modifier
             }
-        } else {
-            animatedSize = null
-            Modifier
-        }
+            .then(
+                // Keep the SizeModifier in the chain and switch between active animating and
+                // passive
+                // observing based on sizeAnimation's value
+                SizeModifierElement(sizeAnimation, sizeTransform)
+            )
     }
 
     // This helps track the target measurable without affecting the placement order. Target
@@ -587,31 +596,88 @@
         }
     }
 
-    private inner class SizeModifier(
-        val sizeAnimation: Transition<S>.DeferredAnimation<IntSize, AnimationVector2D>,
-        val sizeTransform: State<SizeTransform?>,
-    ) : LayoutModifierWithPassThroughIntrinsics() {
+    private inner class SizeModifierElement(
+        val sizeAnimation: Transition<S>.DeferredAnimation<IntSize, AnimationVector2D>?,
+        val sizeTransform: State<SizeTransform?>
+    ) : ModifierNodeElement<SizeModifierNode>() {
+        override fun create(): SizeModifierNode {
+            return SizeModifierNode(sizeAnimation, sizeTransform)
+        }
+
+        override fun hashCode(): Int {
+            return sizeAnimation.hashCode() * 31 + sizeTransform.hashCode()
+        }
+
+        override fun equals(other: Any?): Boolean {
+            return other is AnimatedContentTransitionScopeImpl<*>.SizeModifierElement &&
+                other.sizeAnimation == sizeAnimation &&
+                other.sizeTransform == sizeTransform
+        }
+
+        override fun update(node: SizeModifierNode) {
+            node.sizeAnimation = sizeAnimation
+            node.sizeTransform = sizeTransform
+        }
+
+        override fun InspectorInfo.inspectableProperties() {
+            name = "sizeTransform"
+            properties["sizeAnimation"] = sizeAnimation
+            properties["sizeTransform"] = sizeTransform
+        }
+    }
+
+    private inner class SizeModifierNode(
+        var sizeAnimation: Transition<S>.DeferredAnimation<IntSize, AnimationVector2D>?,
+        var sizeTransform: State<SizeTransform?>,
+    ) : LayoutModifierNodeWithPassThroughIntrinsics() {
+        // This is used to track the on-going size change so that when the target state changes,
+        // we always start from the last seen size to the new target size to ensure continuity.
+        private var lastSize: IntSize = UnspecifiedSize
+
+        private fun lastContinuousSizeOrDefault(default: IntSize) =
+            if (lastSize == UnspecifiedSize) default else lastSize
+
         override fun MeasureScope.measure(
             measurable: Measurable,
             constraints: Constraints
         ): MeasureResult {
             val placeable = measurable.measure(constraints)
-            val size =
-                sizeAnimation.animate(
-                    transitionSpec = {
-                        val initial = targetSizeMap[initialState]?.value ?: IntSize.Zero
-                        val target = targetSizeMap[targetState]?.value ?: IntSize.Zero
-                        sizeTransform.value?.createAnimationSpec(initial, target) ?: spring()
-                    }
-                ) {
-                    targetSizeMap[it]?.value ?: IntSize.Zero
-                }
-            animatedSize = size
             val measuredSize: IntSize
             if (isLookingAhead) {
                 measuredSize = IntSize(placeable.width, placeable.height)
+            } else if (sizeAnimation == null) {
+                // Observing mode
+                measuredSize = IntSize(placeable.width, placeable.height)
+                lastSize = IntSize(placeable.width, placeable.height)
             } else {
+                val currentSize = IntSize(placeable.width, placeable.height)
+                val size =
+                    sizeAnimation!!.animate(
+                        transitionSpec = {
+                            val initial =
+                                if (
+                                    initialState ==
+                                        [email protected]
+                                ) {
+                                    lastContinuousSizeOrDefault(currentSize)
+                                } else {
+                                    targetSizeMap[initialState]?.value ?: IntSize.Zero
+                                }
+                            val target = targetSizeMap[targetState]?.value ?: IntSize.Zero
+                            sizeTransform.value?.createAnimationSpec(initial, target)
+                                ?: spring(stiffness = Spring.StiffnessMediumLow)
+                        }
+                    ) {
+                        // Animate from the approach size to the lookahead size.
+                        if (it == initialState) {
+                            lastContinuousSizeOrDefault(currentSize)
+                        } else {
+                            targetSizeMap[it]?.value ?: IntSize.Zero
+                        }
+                    }
+                animatedSize = size
                 measuredSize = size.value
+                lastSize = size.value
             }
             return layout(measuredSize.width, measuredSize.height) {
                 val offset =
@@ -626,6 +692,8 @@
     }
 }
 
+private val UnspecifiedSize: IntSize = IntSize(Int.MIN_VALUE, Int.MIN_VALUE)
+
 /**
  * Receiver scope for content lambda for AnimatedContent. In this scope,
  * [transition][AnimatedVisibilityScope.transition] can be used to observe the state of the
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimationModifier.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimationModifier.kt
index 57d5833..8102e2c 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimationModifier.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimationModifier.kt
@@ -266,25 +266,3 @@
         width: Int
     ) = measurable.maxIntrinsicHeight(width)
 }
-
-internal abstract class LayoutModifierWithPassThroughIntrinsics : LayoutModifier {
-    final override fun IntrinsicMeasureScope.minIntrinsicWidth(
-        measurable: IntrinsicMeasurable,
-        height: Int
-    ) = measurable.minIntrinsicWidth(height)
-
-    final override fun IntrinsicMeasureScope.minIntrinsicHeight(
-        measurable: IntrinsicMeasurable,
-        width: Int
-    ) = measurable.minIntrinsicHeight(width)
-
-    final override fun IntrinsicMeasureScope.maxIntrinsicWidth(
-        measurable: IntrinsicMeasurable,
-        height: Int
-    ) = measurable.maxIntrinsicWidth(height)
-
-    final override fun IntrinsicMeasureScope.maxIntrinsicHeight(
-        measurable: IntrinsicMeasurable,
-        width: Int
-    ) = measurable.maxIntrinsicHeight(width)
-}
diff --git a/compose/foundation/foundation-layout/build.gradle b/compose/foundation/foundation-layout/build.gradle
index 0fefb0e..a90bb95 100644
--- a/compose/foundation/foundation-layout/build.gradle
+++ b/compose/foundation/foundation-layout/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import androidx.build.PlatformIdentifier
 
@@ -117,6 +119,7 @@
     legacyDisableKotlinStrictApiMode = true
     metalavaK2UastEnabled = false
     samples(project(":compose:foundation:foundation-layout:foundation-layout-samples"))
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/foundation/foundation-layout/samples/build.gradle b/compose/foundation/foundation-layout/samples/build.gradle
index 7b33988..d0a4261 100644
--- a/compose/foundation/foundation-layout/samples/build.gradle
+++ b/compose/foundation/foundation-layout/samples/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 
 plugins {
@@ -50,6 +52,7 @@
     type = LibraryType.SAMPLES
     inceptionYear = "2019"
     description = "Contains the sample code for the Androidx Compose UI Core Layout Classes"
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/foundation/foundation-lint/src/main/java/androidx/compose/foundation/lint/FoundationIssueRegistry.kt b/compose/foundation/foundation-lint/src/main/java/androidx/compose/foundation/lint/FoundationIssueRegistry.kt
index 4aaa5741..93df46c 100644
--- a/compose/foundation/foundation-lint/src/main/java/androidx/compose/foundation/lint/FoundationIssueRegistry.kt
+++ b/compose/foundation/foundation-lint/src/main/java/androidx/compose/foundation/lint/FoundationIssueRegistry.kt
@@ -25,7 +25,7 @@
 /** [IssueRegistry] containing Compose Foundation specific lint issues. */
 class FoundationIssueRegistry : IssueRegistry() {
     // Tests are run with this version. We ensure that with ApiLintVersionsTest
-    override val api = 14
+    override val api = 16
     override val minApi = CURRENT_API
     override val issues
         get() =
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index bf2efe8..d8a1630 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -109,6 +109,7 @@
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public final class ComposeFoundationFlags {
     field public static boolean DragGesturePickUpEnabled;
+    field public static boolean DraggableAddDownEventFixEnabled;
     field public static final androidx.compose.foundation.ComposeFoundationFlags INSTANCE;
     field public static boolean NewNestedFlingPropagationEnabled;
     field public static boolean RemoveBasicTextGraphicsLayerEnabled;
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 4200c0f..ee173f9 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -109,6 +109,7 @@
 
   @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public final class ComposeFoundationFlags {
     field public static boolean DragGesturePickUpEnabled;
+    field public static boolean DraggableAddDownEventFixEnabled;
     field public static final androidx.compose.foundation.ComposeFoundationFlags INSTANCE;
     field public static boolean NewNestedFlingPropagationEnabled;
     field public static boolean RemoveBasicTextGraphicsLayerEnabled;
diff --git a/compose/foundation/foundation/build.gradle b/compose/foundation/foundation/build.gradle
index 196e79c..bd84d75 100644
--- a/compose/foundation/foundation/build.gradle
+++ b/compose/foundation/foundation/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import androidx.build.PlatformIdentifier
 
@@ -156,4 +158,5 @@
     legacyDisableKotlinStrictApiMode = true
     metalavaK2UastEnabled = false
     samples(project(":compose:foundation:foundation:foundation-samples"))
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
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 c33392a..7ff3c28b 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/lint-baseline.xml
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/lint-baseline.xml
@@ -1,14 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.7.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.7.0-alpha02)" variant="all" version="8.7.0-alpha02">
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="            values.removeFirstKt()"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/foundation/demos/text/DrawTextDemo.kt"/>
-    </issue>
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
 
     <issue
         id="NewApi"
@@ -20,15 +11,6 @@
     </issue>
 
     <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="                keys.removeLastKt()"
-        errorLine2="                ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/foundation/demos/text/TextFieldFocusDemo.kt"/>
-    </issue>
-
-    <issue
         id="PrimitiveInCollection"
         message="variable options with type List&lt;? extends Alignment>: replace with FloatList"
         errorLine1="    val options ="
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/collection/ColorList.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/collection/ColorList.kt
index 85264af..aa9c110 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/collection/ColorList.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/collection/ColorList.kt
@@ -24,15 +24,18 @@
     "NOTHING_TO_INLINE",
     "UnusedImport",
 )
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.compose.foundation.demos.collection
 
+import androidx.annotation.IntRange
 import androidx.collection.LongList
 import androidx.collection.MutableLongList
 import androidx.collection.emptyLongList
 import androidx.collection.mutableLongListOf
 import androidx.compose.ui.graphics.Color
 import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmInline
 
@@ -55,21 +58,20 @@
  * calling code must provide the appropriate synchronization. It is also not safe to mutate during
  * reentrancy -- in the middle of a [forEach], for example. However, concurrent reads are safe.
  */
-@OptIn(ExperimentalContracts::class)
 @JvmInline
 internal value class ColorList(val list: LongList) {
     /** The number of elements in the [ColorList]. */
-    @get:androidx.annotation.IntRange(from = 0)
+    @get:IntRange(from = 0)
     public inline val size: Int
         get() = list.size
 
     /** Returns the last valid index in the [ColorList]. This can be `-1` when the list is empty. */
-    @get:androidx.annotation.IntRange(from = -1)
+    @get:IntRange(from = -1)
     public inline val lastIndex: Int
         get() = list.lastIndex
 
     /** Returns an [IntRange] of the valid indices for this [ColorList]. */
-    public inline val indices: IntRange
+    public inline val indices: kotlin.ranges.IntRange
         get() = list.indices
 
     /** Returns `true` if the collection has no elements in it. */
@@ -244,14 +246,14 @@
      * Returns the element at the given [index] or throws [IndexOutOfBoundsException] if the [index]
      * is out of bounds of this collection.
      */
-    public inline operator fun get(@androidx.annotation.IntRange(from = 0) index: Int): Color =
+    public inline operator fun get(@IntRange(from = 0) index: Int): Color =
         Color(list[index].toULong())
 
     /**
      * Returns the element at the given [index] or throws [IndexOutOfBoundsException] if the [index]
      * is out of bounds of this collection.
      */
-    public inline fun elementAt(@androidx.annotation.IntRange(from = 0) index: Int): Color =
+    public inline fun elementAt(@IntRange(from = 0) index: Int): Color =
         Color(list[index].toULong())
 
     /**
@@ -263,7 +265,7 @@
      *   index not in the list.
      */
     public inline fun elementAtOrElse(
-        @androidx.annotation.IntRange(from = 0) index: Int,
+        @IntRange(from = 0) index: Int,
         defaultValue: (index: Int) -> Color
     ): Color = Color(list.elementAtOrElse(index) { defaultValue(it).value.toLong() }.toULong())
 
@@ -350,23 +352,22 @@
  *
  * @constructor Creates a [MutableColorList] with a [capacity] of `initialCapacity`.
  */
-@OptIn(ExperimentalContracts::class)
 @JvmInline
 internal value class MutableColorList(val list: MutableLongList) {
     public constructor(initialCapacity: Int = 16) : this(MutableLongList(initialCapacity))
 
     /** The number of elements in the [ColorList]. */
-    @get:androidx.annotation.IntRange(from = 0)
+    @get:IntRange(from = 0)
     public inline val size: Int
         get() = list.size
 
     /** Returns the last valid index in the [ColorList]. This can be `-1` when the list is empty. */
-    @get:androidx.annotation.IntRange(from = -1)
+    @get:IntRange(from = -1)
     public inline val lastIndex: Int
         get() = list.lastIndex
 
     /** Returns an [IntRange] of the valid indices for this [ColorList]. */
-    public inline val indices: IntRange
+    public inline val indices: kotlin.ranges.IntRange
         get() = list.indices
 
     /** Returns `true` if the collection has no elements in it. */
@@ -541,14 +542,14 @@
      * Returns the element at the given [index] or throws [IndexOutOfBoundsException] if the [index]
      * is out of bounds of this collection.
      */
-    public inline operator fun get(@androidx.annotation.IntRange(from = 0) index: Int): Color =
+    public inline operator fun get(@IntRange(from = 0) index: Int): Color =
         Color(list[index].toULong())
 
     /**
      * Returns the element at the given [index] or throws [IndexOutOfBoundsException] if the [index]
      * is out of bounds of this collection.
      */
-    public inline fun elementAt(@androidx.annotation.IntRange(from = 0) index: Int): Color =
+    public inline fun elementAt(@IntRange(from = 0) index: Int): Color =
         Color(list[index].toULong())
 
     /**
@@ -560,7 +561,7 @@
      *   index not in the list.
      */
     public inline fun elementAtOrElse(
-        @androidx.annotation.IntRange(from = 0) index: Int,
+        @IntRange(from = 0) index: Int,
         defaultValue: (index: Int) -> Color
     ): Color = Color(list.elementAtOrElse(index) { defaultValue(it).value.toLong() }.toULong())
 
@@ -641,7 +642,7 @@
      *
      * @throws IndexOutOfBoundsException if [index] isn't between 0 and [size], inclusive
      */
-    public inline fun add(@androidx.annotation.IntRange(from = 0) index: Int, element: Color) =
+    public inline fun add(@IntRange(from = 0) index: Int, element: Color) =
         list.add(index, element.value.toLong())
 
     /**
@@ -651,10 +652,8 @@
      * @return `true` if the [MutableColorList] was changed or `false` if [elements] was empty
      * @throws IndexOutOfBoundsException if [index] isn't between 0 and [size], inclusive
      */
-    public inline fun addAll(
-        @androidx.annotation.IntRange(from = 0) index: Int,
-        elements: ColorList
-    ): Boolean = list.addAll(index, elements.list)
+    public inline fun addAll(@IntRange(from = 0) index: Int, elements: ColorList): Boolean =
+        list.addAll(index, elements.list)
 
     /**
      * Adds all [elements] to the [MutableColorList] at the given [index], shifting over any
@@ -663,10 +662,8 @@
      * @return `true` if the [MutableColorList] was changed or `false` if [elements] was empty
      * @throws IndexOutOfBoundsException if [index] isn't between 0 and [size], inclusive
      */
-    public inline fun addAll(
-        @androidx.annotation.IntRange(from = 0) index: Int,
-        elements: MutableColorList
-    ): Boolean = list.addAll(index, elements.list)
+    public inline fun addAll(@IntRange(from = 0) index: Int, elements: MutableColorList): Boolean =
+        list.addAll(index, elements.list)
 
     /**
      * Adds all [elements] to the end of the [MutableColorList] and returns `true` if the
@@ -747,7 +744,7 @@
      *
      * @throws IndexOutOfBoundsException if [index] isn't between 0 and [lastIndex], inclusive
      */
-    public inline fun removeAt(@androidx.annotation.IntRange(from = 0) index: Int): Color =
+    public inline fun removeAt(@IntRange(from = 0) index: Int): Color =
         Color(list.removeAt(index).toULong())
 
     /**
@@ -756,10 +753,8 @@
      * @throws IndexOutOfBoundsException if [start] or [end] isn't between 0 and [size], inclusive
      * @throws IllegalArgumentException if [start] is greater than [end]
      */
-    public inline fun removeRange(
-        @androidx.annotation.IntRange(from = 0) start: Int,
-        @androidx.annotation.IntRange(from = 0) end: Int
-    ) = list.removeRange(start, end)
+    public inline fun removeRange(@IntRange(from = 0) start: Int, @IntRange(from = 0) end: Int) =
+        list.removeRange(start, end)
 
     /**
      * Keeps only [elements] in the [MutableColorList] and removes all other values.
@@ -781,10 +776,8 @@
      * @return the previous value set at [index]
      * @throws IndexOutOfBoundsException if [index] isn't between 0 and [lastIndex], inclusive
      */
-    public inline operator fun set(
-        @androidx.annotation.IntRange(from = 0) index: Int,
-        element: Color
-    ): Color = Color(list.set(index, element.value.toLong()).toULong())
+    public inline operator fun set(@IntRange(from = 0) index: Int, element: Color): Color =
+        Color(list.set(index, element.value.toLong()).toULong())
 }
 
 /** @return a read-only [ColorList] with nothing in it. */
@@ -833,3 +826,35 @@
     MutableColorList(
         mutableLongListOf(element1.value.toLong(), element2.value.toLong(), element3.value.toLong())
     )
+
+/**
+ * Builds a new [ColorList] by populating a [MutableColorList] using the given [builderAction].
+ *
+ * The list passed as a receiver to the [builderAction] is valid only inside that function. Using it
+ * outside of the function produces an unspecified behavior.
+ *
+ * @param builderAction Lambda in which the [MutableColorList] can be populated.
+ */
+internal inline fun buildColorList(
+    builderAction: MutableColorList.() -> Unit,
+): ColorList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableColorList().apply(builderAction).asColorList()
+}
+
+/**
+ * Builds a new [ColorList] by populating a [MutableColorList] using the given [builderAction].
+ *
+ * The list passed as a receiver to the [builderAction] is valid only inside that function. Using it
+ * outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ * @param builderAction Lambda in which the [MutableColorList] can be populated.
+ */
+internal inline fun buildColorList(
+    initialCapacity: Int,
+    builderAction: MutableColorList.() -> Unit,
+): ColorList {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableColorList(initialCapacity).apply(builderAction).asColorList()
+}
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/collection/ColorSet.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/collection/ColorSet.kt
index 20479137..4ceecf1 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/collection/ColorSet.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/collection/ColorSet.kt
@@ -24,15 +24,18 @@
     "NOTHING_TO_INLINE",
     "UnusedImport",
 )
+@file:OptIn(ExperimentalContracts::class)
 
 package androidx.compose.foundation.demos.collection
 
+import androidx.annotation.IntRange
 import androidx.collection.LongSet
 import androidx.collection.MutableLongSet
 import androidx.collection.emptyLongSet
 import androidx.collection.mutableLongSetOf
 import androidx.compose.ui.graphics.Color
 import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
 import kotlin.contracts.contract
 import kotlin.jvm.JvmInline
 
@@ -103,6 +106,35 @@
     )
 
 /**
+ * Builds a new [ColorSet] by populating a [MutableColorSet] using the given [builderAction].
+ *
+ * The set passed as a receiver to the [builderAction] is valid only inside that function. Using it
+ * outside of the function produces an unspecified behavior.
+ */
+internal inline fun buildColorSet(
+    builderAction: MutableColorSet.() -> Unit,
+): ColorSet {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableColorSet().apply(builderAction).asColorSet()
+}
+
+/**
+ * Builds a new [ColorSet] by populating a [MutableColorSet] using the given [builderAction].
+ *
+ * The set passed as a receiver to the [builderAction] is valid only inside that function. Using it
+ * outside of the function produces an unspecified behavior.
+ *
+ * @param initialCapacity Hint for the expected number of elements added in the [builderAction].
+ */
+internal inline fun buildColorSet(
+    initialCapacity: Int,
+    builderAction: MutableColorSet.() -> Unit,
+): ColorSet {
+    contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
+    return MutableColorSet(initialCapacity).apply(builderAction).asColorSet()
+}
+
+/**
  * [ColorSet] is a container with a [Set]-like interface designed to avoid allocations, including
  * boxing.
  *
@@ -116,19 +148,18 @@
  *
  * @see [MutableColorSet]
  */
-@OptIn(ExperimentalContracts::class)
 @JvmInline
 internal value class ColorSet(val set: LongSet) {
     /**
      * Returns the number of elements that can be stored in this set without requiring internal
      * storage reallocation.
      */
-    @get:androidx.annotation.IntRange(from = 0)
+    @get:IntRange(from = 0)
     public inline val capacity: Int
         get() = set.capacity
 
     /** Returns the number of elements in this set. */
-    @get:androidx.annotation.IntRange(from = 0)
+    @get:IntRange(from = 0)
     public inline val size: Int
         get() = set.size
 
@@ -200,7 +231,7 @@
     }
 
     /** Returns the number of elements in this set. */
-    @androidx.annotation.IntRange(from = 0) public inline fun count(): Int = set.count()
+    @IntRange(from = 0) public inline fun count(): Int = set.count()
 
     /**
      * Returns the number of elements matching the given [predicate].
@@ -208,7 +239,7 @@
      * @param predicate Called for all elements in the set to count the number for which it returns
      *   `true`.
      */
-    @androidx.annotation.IntRange(from = 0)
+    @IntRange(from = 0)
     public inline fun count(predicate: (element: Color) -> Boolean): Int {
         contract { callsInPlace(predicate) }
         return set.count { predicate(Color(it.toULong())) }
@@ -256,19 +287,18 @@
  * and one or more threads modify the structure of the set (insertion or removal for instance), the
  * calling code must provide the appropriate synchronization. Concurrent reads are however safe.
  */
-@OptIn(ExperimentalContracts::class)
 @JvmInline
 internal value class MutableColorSet(val set: MutableLongSet) {
     /**
      * Returns the number of elements that can be stored in this set without requiring internal
      * storage reallocation.
      */
-    @get:androidx.annotation.IntRange(from = 0)
+    @get:IntRange(from = 0)
     public inline val capacity: Int
         get() = set.capacity
 
     /** Returns the number of elements in this set. */
-    @get:androidx.annotation.IntRange(from = 0)
+    @get:IntRange(from = 0)
     public inline val size: Int
         get() = set.size
 
@@ -340,7 +370,7 @@
     }
 
     /** Returns the number of elements in this set. */
-    @androidx.annotation.IntRange(from = 0) public inline fun count(): Int = set.count()
+    @IntRange(from = 0) public inline fun count(): Int = set.count()
 
     /**
      * Returns the number of elements matching the given [predicate].
@@ -348,7 +378,7 @@
      * @param predicate Called for all elements in the set to count the number for which it returns
      *   `true`.
      */
-    @androidx.annotation.IntRange(from = 0)
+    @IntRange(from = 0)
     public inline fun count(predicate: (element: Color) -> Boolean): Int {
         contract { callsInPlace(predicate) }
         return set.count { predicate(Color(it.toULong())) }
@@ -485,5 +515,5 @@
      * Returns the number of empty elements removed from this set's storage. Returns 0 if no
      * trimming is necessary or possible.
      */
-    @androidx.annotation.IntRange(from = 0) public inline fun trim(): Int = set.trim()
+    @IntRange(from = 0) public inline fun trim(): Int = set.trim()
 }
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeMinTouchTargetTextSelectionDemo.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeMinTouchTargetTextSelectionDemo.kt
index 65d6c77..bc32206 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeMinTouchTargetTextSelectionDemo.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/ComposeMinTouchTargetTextSelectionDemo.kt
@@ -17,7 +17,7 @@
 package androidx.compose.foundation.demos.text
 
 import androidx.compose.foundation.border
-import androidx.compose.foundation.demos.collection.MutableColorList
+import androidx.compose.foundation.demos.collection.buildColorList
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
@@ -61,16 +61,14 @@
 
 // red is used for the selection container color
 private val Rainbow =
-    MutableColorList(initialCapacity = 6)
-        .apply {
-            add(Orange)
-            add(Yellow)
-            add(Green)
-            add(Blue)
-            add(Indigo)
-            add(Purple)
-        }
-        .asColorList()
+    buildColorList(initialCapacity = 6) {
+        add(Orange)
+        add(Yellow)
+        add(Green)
+        add(Blue)
+        add(Indigo)
+        add(Purple)
+    }
 
 @Composable
 fun MinTouchTargetTextSelection() {
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/lint-baseline.xml b/compose/foundation/foundation/integration-tests/lazy-tests/lint-baseline.xml
index 8419568..f303dca 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/lint-baseline.xml
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/lint-baseline.xml
@@ -1,41 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.7.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.7.0-alpha02)" variant="all" version="8.7.0-alpha02">
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="        rule.runOnIdle { items.removeLastKt() }"
-        errorLine2="                         ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyColumnTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="                handles.removeFirstKt().release()"
-        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridPinnableContainerTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="                handles.removeFirstKt().release()"
-        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListPinnableContainerTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="                handles.removeFirstKt().release()"
-        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridPinnableContainerTest.kt"/>
-    </issue>
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
 
     <issue
         id="BanThreadSleep"
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt
index 84d022c..59530fe 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutTest.kt
@@ -47,6 +47,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -418,6 +419,7 @@
         rule.runOnIdle { assertThat(remeasuresCount).isEqualTo(1) }
     }
 
+    @Ignore("b/369188686")
     @Test
     fun nodeIsReusedWhenRemovedFirst() {
         var itemCount by mutableStateOf(1)
diff --git a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt
index 5eb45a6..d2cea9b 100644
--- a/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt
+++ b/compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt
@@ -59,6 +59,7 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.testutils.WithTouchSlop
 import androidx.compose.testutils.assertPixels
 import androidx.compose.testutils.assertShape
@@ -119,6 +120,7 @@
 import java.util.concurrent.CountDownLatch
 import kotlin.math.abs
 import kotlin.math.roundToInt
+import kotlin.random.Random
 import kotlin.test.assertEquals
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
@@ -2789,6 +2791,36 @@
         rule.onNodeWithTag(LazyListTag).assertStartPositionInRootIsEqualTo(itemSize)
     }
 
+    @Test
+    fun reorderingInLookeahead() {
+        var items by mutableStateOf(List(500) { it })
+
+        val itemSizePx = 50f
+        val itemSize = with(rule.density) { itemSizePx.toDp() }
+
+        rule.setContent {
+            LookaheadScope {
+                LazyColumnOrRow(Modifier.mainAxisSize(itemSize * 2)) {
+                    items(items, key = { it }) {
+                        Box(Modifier.animateItem().mainAxisSize(itemSize)) {
+                            Box { BasicText("Item $it") }
+                        }
+                    }
+                }
+            }
+        }
+
+        val random = Random(42)
+        repeat(20) {
+            val newItems = items.shuffled(random)
+            items = newItems
+            rule.runOnUiThread {
+                Snapshot.sendApplyNotifications()
+                rule.mainClock.advanceTimeByFrame()
+            }
+        }
+    }
+
     // ********************* END OF TESTS *********************
     // Helper functions, etc. live below here
 
diff --git a/compose/foundation/foundation/lint-baseline.xml b/compose/foundation/foundation/lint-baseline.xml
index 08b3866..486d843 100644
--- a/compose/foundation/foundation/lint-baseline.xml
+++ b/compose/foundation/foundation/lint-baseline.xml
@@ -1,50 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.7.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.7.0-alpha02)" variant="all" version="8.7.0-alpha02">
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="        assertThat(calls.removeFirstKt()).isEqualTo(description)"
-        errorLine2="                   ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/FakeInputMethodManager.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="                handles.removeFirstKt().release()"
-        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerPinnableContainerTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="            undoStack.removeFirstKt()"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/undo/UndoManager.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="        val topOperation = undoStack.removeLastKt()"
-        errorLine2="                           ~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/undo/UndoManager.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="        val topOperation = redoStack.removeLastKt()"
-        errorLine2="                           ~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/undo/UndoManager.kt"/>
-    </issue>
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
 
     <issue
         id="PrimitiveInCollection"
diff --git a/compose/foundation/foundation/samples/build.gradle b/compose/foundation/foundation/samples/build.gradle
index b2239a0..5f42e6a 100644
--- a/compose/foundation/foundation/samples/build.gradle
+++ b/compose/foundation/foundation/samples/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 
 plugins {
@@ -53,6 +55,7 @@
     type = LibraryType.SAMPLES
     inceptionYear = "2019"
     description = "Contains the sample code for the Androidx Compose UI Foundational Components"
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyLayoutSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyLayoutSamples.kt
new file mode 100644
index 0000000..d418c0a
--- /dev/null
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyLayoutSamples.kt
@@ -0,0 +1,156 @@
+/*
+ * 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.samples
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.layout.LazyLayout
+import androidx.compose.foundation.lazy.layout.LazyLayoutItemProvider
+import androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScope
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.dp
+
+private val Items = (0..100).toList().map { it.toString() }
+
+/**
+ * In this example, the lazy layout simply lays out content based on its visibility on the screen.
+ */
+@Composable
+@Preview
+fun LazyLayoutDisplayVisibleItemsOnlySample() {
+    BasicNonScrollableLazyLayout(
+        modifier = Modifier.size(500.dp),
+        orientation = Orientation.Vertical,
+        items = Items
+    ) { item, index ->
+        Box(
+            modifier =
+                Modifier.width(100.dp)
+                    .height(100.dp)
+                    .background(color = if (index % 2 == 0) Color.Red else Color.Green)
+        ) {
+            Text(text = item)
+        }
+    }
+}
+
+/**
+ * A simple Layout that will place items top to down, or right to left, without any scrolling or
+ * offset until they fit the viewport.
+ */
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+private fun <T> BasicNonScrollableLazyLayout(
+    items: List<T>,
+    modifier: Modifier = Modifier,
+    orientation: Orientation = Orientation.Vertical,
+    content: @Composable (item: T, index: Int) -> Unit
+) {
+    val measurePolicy = remember(items, orientation) { basicMeasurePolicy(orientation, items.size) }
+    val itemProvider = remember(items, content) { { BasicLazyLayoutItemProvider(items, content) } }
+    LazyLayout(modifier = modifier, itemProvider = itemProvider, measurePolicy = measurePolicy)
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+private fun basicMeasurePolicy(
+    orientation: Orientation,
+    itemCount: Int
+): LazyLayoutMeasureScope.(Constraints) -> MeasureResult = { constraints ->
+    fun Placeable.mainAxisSize() = if (orientation == Orientation.Vertical) height else width
+    fun Placeable.crossAxisSize() = if (orientation == Orientation.Vertical) width else height
+
+    val viewportSize =
+        if (orientation == Orientation.Vertical) constraints.maxHeight else constraints.maxWidth
+
+    val childConstraints =
+        Constraints(
+            maxWidth =
+                if (orientation == Orientation.Vertical) viewportSize else Constraints.Infinity,
+            maxHeight =
+                if (orientation == Orientation.Horizontal) viewportSize else Constraints.Infinity
+        )
+
+    var currentItemIndex = 0
+    // saves placeables and their main axis position
+    val placeables = mutableListOf<Pair<Placeable, Int>>()
+    var crossAxisSize = 0
+    var mainAxisSize = 0
+
+    // measure items until we either fill in the space or run out of items.
+    while (mainAxisSize < viewportSize && currentItemIndex < itemCount) {
+        val itemPlaceables = measure(currentItemIndex, childConstraints)
+        for (item in itemPlaceables) {
+            // save placeable to be placed later.
+            placeables.add(item to mainAxisSize)
+
+            mainAxisSize += item.mainAxisSize() // item size contributes to main axis size
+            // cross axis size will the size of tallest/widest item
+            crossAxisSize = maxOf(crossAxisSize, item.crossAxisSize())
+        }
+        currentItemIndex++
+    }
+
+    val layoutWidth =
+        if (orientation == Orientation.Horizontal) {
+            minOf(mainAxisSize, viewportSize)
+        } else {
+            crossAxisSize
+        }
+    val layoutHeight =
+        if (orientation == Orientation.Vertical) {
+            minOf(mainAxisSize, viewportSize)
+        } else {
+            crossAxisSize
+        }
+
+    layout(layoutWidth, layoutHeight) {
+        // since this is a linear list all items are placed on the same cross-axis position
+        for ((placeable, position) in placeables) {
+            if (orientation == Orientation.Vertical) {
+                placeable.place(0, position)
+            } else {
+                placeable.place(position, 0)
+            }
+        }
+    }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+private class BasicLazyLayoutItemProvider<T>(
+    private val items: List<T>,
+    private val content: @Composable (item: T, index: Int) -> Unit
+) : LazyLayoutItemProvider {
+    override val itemCount: Int = items.size
+
+    @Composable
+    override fun Item(index: Int, key: Any) {
+        content.invoke(items[index], index)
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/ComposeFoundationFlags.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/ComposeFoundationFlags.kt
index 1204039..51667ce 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/ComposeFoundationFlags.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/ComposeFoundationFlags.kt
@@ -81,4 +81,14 @@
      * continue the gesture until all pointers are up.
      */
     @Suppress("MutableBareField") @JvmField var DragGesturePickUpEnabled = true
+
+    /**
+     * Selecting flag to enable the Draggable fix for missing down events. This will influence the
+     * velocity generated after a Drag gesture. Because the down event is used by the velocity
+     * tracker to calculate the resulting velocity of a drag gesture, disabling this flag will incur
+     * in velocities being smaller than they're supposed to be. Enabling the flag will return the
+     * original behavior where velocities are on par with the ones generated by a similar gesture in
+     * the views framework version of the velocity tracker.
+     */
+    @Suppress("MutableBareField") @JvmField var DraggableAddDownEventFixEnabled = true
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/DragGestureDetector.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/DragGestureDetector.kt
index 2ae7b1d..0bff733 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/DragGestureDetector.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/DragGestureDetector.kt
@@ -25,6 +25,7 @@
 //  functions public
 
 import androidx.compose.foundation.ComposeFoundationFlags.DragGesturePickUpEnabled
+import androidx.compose.foundation.ComposeFoundationFlags.DraggableAddDownEventFixEnabled
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.isSpecified
@@ -167,20 +168,32 @@
  * @see detectHorizontalDragGestures
  * @see detectDragGesturesAfterLongPress to detect gestures after long press
  */
+@OptIn(ExperimentalFoundationApi::class)
 suspend fun PointerInputScope.detectDragGestures(
     onDragStart: (Offset) -> Unit = {},
     onDragEnd: () -> Unit = {},
     onDragCancel: () -> Unit = {},
     onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit
 ) =
-    detectDragGestures(
-        onDragStart = { _, slopTriggerChange, _ -> onDragStart(slopTriggerChange.position) },
-        onDragEnd = { onDragEnd.invoke() },
-        onDragCancel = onDragCancel,
-        shouldAwaitTouchSlop = { true },
-        orientationLock = null,
-        onDrag = onDrag
-    )
+    if (DraggableAddDownEventFixEnabled) {
+        detectDragGestures(
+            onDragStart = { _, slopTriggerChange, _ -> onDragStart(slopTriggerChange.position) },
+            onDragEnd = { onDragEnd.invoke() },
+            onDragCancel = onDragCancel,
+            shouldAwaitTouchSlop = { true },
+            orientationLock = null,
+            onDrag = onDrag
+        )
+    } else {
+        legacyDetectDragGestures(
+            onDragStart = { change, _ -> onDragStart(change.position) },
+            onDragEnd = { onDragEnd.invoke() },
+            onDragCancel = onDragCancel,
+            shouldAwaitTouchSlop = { true },
+            orientationLock = null,
+            onDrag = onDrag
+        )
+    }
 
 /**
  * A Gesture detector that waits for pointer down and touch slop in the direction specified by
@@ -317,6 +330,67 @@
     }
 }
 
+internal suspend fun PointerInputScope.legacyDetectDragGestures(
+    onDragStart: (change: PointerInputChange, initialDelta: Offset) -> Unit,
+    onDragEnd: (change: PointerInputChange) -> Unit,
+    onDragCancel: () -> Unit,
+    shouldAwaitTouchSlop: () -> Boolean,
+    orientationLock: Orientation?,
+    onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit
+) {
+    var overSlop: Offset
+
+    awaitEachGesture {
+        val initialDown = awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Initial)
+        val awaitTouchSlop = shouldAwaitTouchSlop()
+
+        if (!awaitTouchSlop) {
+            initialDown.consume()
+        }
+        val down = awaitFirstDown(requireUnconsumed = false)
+        var drag: PointerInputChange?
+        var initialDelta = Offset.Zero
+        overSlop = Offset.Zero
+
+        if (awaitTouchSlop) {
+            do {
+                drag =
+                    awaitPointerSlopOrCancellation(
+                        down.id,
+                        down.type,
+                        orientation = orientationLock
+                    ) { change, over ->
+                        change.consume()
+                        overSlop = over
+                    }
+            } while (drag != null && !drag.isConsumed)
+            initialDelta = overSlop
+        } else {
+            drag = initialDown
+        }
+
+        if (drag != null) {
+            onDragStart.invoke(drag, initialDelta)
+            onDrag(drag, overSlop)
+            val upEvent =
+                drag(
+                    pointerId = drag.id,
+                    onDrag = {
+                        onDrag(it, it.positionChange())
+                        it.consume()
+                    },
+                    orientation = orientationLock,
+                    motionConsumed = { it.isConsumed }
+                )
+            if (upEvent == null) {
+                onDragCancel()
+            } else {
+                onDragEnd(upEvent)
+            }
+        }
+    }
+}
+
 /**
  * Gesture detector that waits for pointer down and long press, after which it calls [onDrag] for
  * each drag event.
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 82ad253..7da2e3e 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
@@ -16,6 +16,8 @@
 
 package androidx.compose.foundation.gestures
 
+import androidx.compose.foundation.ComposeFoundationFlags.DraggableAddDownEventFixEnabled
+import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.MutatePriority
 import androidx.compose.foundation.MutatorMutex
 import androidx.compose.foundation.gestures.DragEvent.DragCancelled
@@ -48,6 +50,7 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.Velocity
 import kotlin.coroutines.cancellation.CancellationException
+import kotlin.math.sign
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.coroutineScope
@@ -457,6 +460,7 @@
         pointerInputNode?.onPointerEvent(pointerEvent, pass, bounds)
     }
 
+    @OptIn(ExperimentalFoundationApi::class)
     private fun initializePointerInputNode(): SuspendingPointerInputModifierNode {
         return SuspendingPointerInputModifierNode {
             // re-create tracker when pointer input block restarts. This lazily creates the tracker
@@ -486,6 +490,26 @@
                     }
                 }
 
+            val onLegacyDragStart: (change: PointerInputChange, initialDelta: Offset) -> Unit =
+                { startEvent, initialDelta ->
+                    if (canDrag.invoke(startEvent)) {
+                        if (!isListeningForEvents) {
+                            if (channel == null) {
+                                channel = Channel(capacity = Channel.UNLIMITED)
+                            }
+                            startListeningForEvents()
+                        }
+                        val overSlopOffset = initialDelta
+                        val xSign = sign(startEvent.position.x)
+                        val ySign = sign(startEvent.position.y)
+                        val adjustedStart =
+                            startEvent.position -
+                                Offset(overSlopOffset.x * xSign, overSlopOffset.y * ySign)
+
+                        channel?.trySend(DragStarted(adjustedStart))
+                    }
+                }
+
             val onDragEnd: (change: PointerInputChange) -> Unit = { upEvent ->
                 velocityTracker.addPointerInputChange(upEvent)
                 val maximumVelocity = currentValueOf(LocalViewConfiguration).maximumFlingVelocity
@@ -507,14 +531,25 @@
 
             coroutineScope {
                 try {
-                    detectDragGestures(
-                        orientationLock = orientationLock,
-                        onDragStart = onDragStart,
-                        onDragEnd = onDragEnd,
-                        onDragCancel = onDragCancel,
-                        shouldAwaitTouchSlop = shouldAwaitTouchSlop,
-                        onDrag = onDrag
-                    )
+                    if (DraggableAddDownEventFixEnabled) {
+                        detectDragGestures(
+                            orientationLock = orientationLock,
+                            onDragStart = onDragStart,
+                            onDragEnd = onDragEnd,
+                            onDragCancel = onDragCancel,
+                            shouldAwaitTouchSlop = shouldAwaitTouchSlop,
+                            onDrag = onDrag
+                        )
+                    } else {
+                        legacyDetectDragGestures(
+                            orientationLock = orientationLock,
+                            onDragStart = onLegacyDragStart,
+                            onDragEnd = onDragEnd,
+                            onDragCancel = onDragCancel,
+                            shouldAwaitTouchSlop = shouldAwaitTouchSlop,
+                            onDrag = onDrag
+                        )
+                    }
                 } catch (cancellation: CancellationException) {
                     channel?.trySend(DragCancelled)
                     if (!isActive) throw cancellation
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory.kt
index 3cb700c..2c1bc66 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutItemContentFactory.kt
@@ -20,7 +20,6 @@
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.ReusableContentHost
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.saveable.SaveableStateHolder
 import kotlin.jvm.JvmInline
@@ -94,7 +93,7 @@
                     if (index != -1) this.index = index
                 }
 
-                ReusableContentHost(active = index != -1) {
+                if (index != -1) {
                     SkippableItem(
                         itemProvider,
                         StableValue(saveableStateHolder),
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem.kt
index 354e747..341a977 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem.kt
@@ -21,7 +21,6 @@
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.DisposableEffect
 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
@@ -101,16 +100,16 @@
     private val pinnedItemList: LazyLayoutPinnedItemList,
 ) : PinnableContainer, PinnableContainer.PinnedHandle, LazyLayoutPinnedItemList.PinnedItem {
     /** Current index associated with this item. */
-    override var index by mutableIntStateOf(-1)
+    override var index = -1
 
     /**
      * It is a valid use case when users of this class call [pin] multiple times individually, so we
      * want to do the unpinning only when all of the users called [release].
      */
-    private var pinsCount by mutableIntStateOf(0)
+    private var pinsCount = 0
 
     /** Handle associated with the current [parentPinnableContainer]. */
-    private var parentHandle by mutableStateOf<PinnableContainer.PinnedHandle?>(null)
+    private var parentHandle: PinnableContainer.PinnedHandle? = null
 
     /**
      * Current parent [PinnableContainer]. Note that we should correctly re-pin if we pinned the
diff --git a/compose/foundation/foundation/src/commonStubsMain/kotlin/androidx/compose/foundation/draganddrop/DragAndDropSource.commonStubs.kt b/compose/foundation/foundation/src/commonStubsMain/kotlin/androidx/compose/foundation/draganddrop/DragAndDropSource.commonStubs.kt
index f942926..983e024 100644
--- a/compose/foundation/foundation/src/commonStubsMain/kotlin/androidx/compose/foundation/draganddrop/DragAndDropSource.commonStubs.kt
+++ b/compose/foundation/foundation/src/commonStubsMain/kotlin/androidx/compose/foundation/draganddrop/DragAndDropSource.commonStubs.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT")
+
 package androidx.compose.foundation.draganddrop
 
 import androidx.compose.foundation.implementedInJetBrainsFork
diff --git a/compose/lint/common-test/build.gradle b/compose/lint/common-test/build.gradle
index baf4218..1b5afba 100644
--- a/compose/lint/common-test/build.gradle
+++ b/compose/lint/common-test/build.gradle
@@ -30,8 +30,8 @@
 
 dependencies {
     implementation(libs.kotlinStdlib)
-    api(libs.androidLint)
-    api(libs.androidLintTests)
+    api(libs.androidLintPrev)
+    api(libs.androidLintPrevTests)
     api(libs.junit)
     api(libs.truth)
 }
diff --git a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
index 7baadf7..5a6c08c 100644
--- a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
+++ b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
@@ -25,7 +25,7 @@
 
 class ComposeIssueRegistry : IssueRegistry() {
     override val minApi = CURRENT_API
-    override val api = 14
+    override val api = 16
     override val issues
         get(): List<Issue> {
             return listOf(
diff --git a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/LambdaStructuralEqualityDetector.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/LambdaStructuralEqualityDetector.kt
index 5ae70d2..e85de35 100644
--- a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/LambdaStructuralEqualityDetector.kt
+++ b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/LambdaStructuralEqualityDetector.kt
@@ -85,7 +85,7 @@
         }
 
     private fun KtExpression.isFunctionType(): Boolean =
-        analyze(this) { getKtType()?.isFunctionType == true }
+        analyze(this) { expressionType?.isFunctionType == true }
 
     companion object {
         private const val BriefDescription =
diff --git a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/UnnecessaryLambdaCreationDetector.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/UnnecessaryLambdaCreationDetector.kt
index b883529..610db683 100644
--- a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/UnnecessaryLambdaCreationDetector.kt
+++ b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/UnnecessaryLambdaCreationDetector.kt
@@ -27,11 +27,12 @@
 import com.android.tools.lint.detector.api.Scope
 import com.android.tools.lint.detector.api.Severity
 import com.android.tools.lint.detector.api.SourceCodeScanner
-import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
+import org.jetbrains.kotlin.analysis.api.KaSession
 import org.jetbrains.kotlin.analysis.api.analyze
-import org.jetbrains.kotlin.analysis.api.calls.KtSimpleFunctionCall
 import org.jetbrains.kotlin.analysis.api.calls.singleFunctionCallOrNull
-import org.jetbrains.kotlin.analysis.api.types.KtFunctionalType
+import org.jetbrains.kotlin.analysis.api.resolution.KaSimpleFunctionCall
+import org.jetbrains.kotlin.analysis.api.resolution.singleFunctionCallOrNull
+import org.jetbrains.kotlin.analysis.api.types.KaFunctionType
 import org.jetbrains.kotlin.psi.KtCallElement
 import org.jetbrains.kotlin.psi.KtLambdaExpression
 import org.jetbrains.kotlin.util.OperatorNameConventions
@@ -121,7 +122,7 @@
             analyze(expressionSourcePsi) {
                 val functionType = dispatchReceiverType(expressionSourcePsi) ?: return
                 val argumentType = toLambdaFunctionalType(node) ?: return
-                if (!(functionType isSubTypeOf argumentType)) return
+                if (!(functionType.isSubtypeOf(argumentType))) return
             }
 
             val expectedComposable = node.isComposable
@@ -192,18 +193,16 @@
     }
 }
 
-private fun KtAnalysisSession.dispatchReceiverType(callElement: KtCallElement): KtFunctionalType? =
+private fun KaSession.dispatchReceiverType(callElement: KtCallElement): KaFunctionType? =
     callElement
-        .resolveCall()
+        .resolveToCall()
         ?.singleFunctionCallOrNull()
-        ?.takeIf { it is KtSimpleFunctionCall && it.isImplicitInvoke }
+        ?.takeIf { it is KaSimpleFunctionCall && it.isImplicitInvoke }
         ?.partiallyAppliedSymbol
         ?.dispatchReceiver
-        ?.type as? KtFunctionalType
+        ?.type as? KaFunctionType
 
-private fun KtAnalysisSession.toLambdaFunctionalType(
-    lambdaExpression: ULambdaExpression
-): KtFunctionalType? {
+private fun KaSession.toLambdaFunctionalType(lambdaExpression: ULambdaExpression): KaFunctionType? {
     val sourcePsi = lambdaExpression.sourcePsi as? KtLambdaExpression ?: return null
-    return sourcePsi.getKtType() as? KtFunctionalType
+    return sourcePsi.expressionType as? KaFunctionType
 }
diff --git a/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/ApiLintVersionsTest.kt b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/ApiLintVersionsTest.kt
index bc20b91..d7f44e5 100644
--- a/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/ApiLintVersionsTest.kt
+++ b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/ApiLintVersionsTest.kt
@@ -32,6 +32,6 @@
 
         val registry = ComposeIssueRegistry()
         assertThat(registry.api).isEqualTo(CURRENT_API)
-        assertThat(registry.minApi).isEqualTo(14)
+        assertThat(registry.minApi).isEqualTo(16)
     }
 }
diff --git a/compose/material/material-lint/src/main/java/androidx/compose/material/lint/MaterialIssueRegistry.kt b/compose/material/material-lint/src/main/java/androidx/compose/material/lint/MaterialIssueRegistry.kt
index 5977c35..ae21095 100644
--- a/compose/material/material-lint/src/main/java/androidx/compose/material/lint/MaterialIssueRegistry.kt
+++ b/compose/material/material-lint/src/main/java/androidx/compose/material/lint/MaterialIssueRegistry.kt
@@ -23,7 +23,7 @@
 /** [IssueRegistry] containing Material specific lint issues. */
 class MaterialIssueRegistry : IssueRegistry() {
     // Tests are run with this version. We ensure that with ApiLintVersionsTest
-    override val api = 14
+    override val api = 16
     override val minApi = CURRENT_API
     override val issues
         get() =
diff --git a/compose/material/material-ripple/build.gradle b/compose/material/material-ripple/build.gradle
index 227c21a..b6d5e76 100644
--- a/compose/material/material-ripple/build.gradle
+++ b/compose/material/material-ripple/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import androidx.build.PlatformIdentifier
 
@@ -111,6 +113,7 @@
     description = "Material ripple used to build interactive components"
     legacyDisableKotlinStrictApiMode = true
     metalavaK2UastEnabled = false
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/material/material/build.gradle b/compose/material/material/build.gradle
index ec7ba65..f6eb550 100644
--- a/compose/material/material/build.gradle
+++ b/compose/material/material/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import androidx.build.PlatformIdentifier
 
@@ -137,6 +139,7 @@
     legacyDisableKotlinStrictApiMode = true
     metalavaK2UastEnabled = false
     samples(project(":compose:material:material:material-samples"))
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 // Screenshot tests related setup
diff --git a/compose/material/material/samples/build.gradle b/compose/material/material/samples/build.gradle
index cc8ffef..84b77f2 100644
--- a/compose/material/material/samples/build.gradle
+++ b/compose/material/material/samples/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 
 plugins {
@@ -52,6 +54,7 @@
     mavenVersion = LibraryVersions.COMPOSE
     inceptionYear = "2019"
     description = "Contains the sample code for the AndroidX Compose Material components."
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/material3/adaptive/adaptive-layout/api/current.txt b/compose/material3/adaptive/adaptive-layout/api/current.txt
index 74fbeb5..35ab398 100644
--- a/compose/material3/adaptive/adaptive-layout/api/current.txt
+++ b/compose/material3/adaptive/adaptive-layout/api/current.txt
@@ -125,6 +125,30 @@
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public interface PaneMotion {
     method public androidx.compose.animation.EnterTransition getEnterTransition(androidx.compose.material3.adaptive.layout.PaneScaffoldMotionScope);
     method public androidx.compose.animation.ExitTransition getExitTransition(androidx.compose.material3.adaptive.layout.PaneScaffoldMotionScope);
+    field public static final androidx.compose.material3.adaptive.layout.PaneMotion.Companion Companion;
+  }
+
+  public static final class PaneMotion.Companion {
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getAnimateBounds();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getEnterFromLeft();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getEnterFromLeftDelayed();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getEnterFromRight();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getEnterFromRightDelayed();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getEnterWithExpand();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getExitToLeft();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getExitToRight();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getExitWithShrink();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getNoMotion();
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion AnimateBounds;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion EnterFromLeft;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion EnterFromLeftDelayed;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion EnterFromRight;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion EnterFromRightDelayed;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion EnterWithExpand;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion ExitToLeft;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion ExitToRight;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion ExitWithShrink;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion NoMotion;
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class PaneMotionData {
diff --git a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
index 74fbeb5..35ab398 100644
--- a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
+++ b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
@@ -125,6 +125,30 @@
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public interface PaneMotion {
     method public androidx.compose.animation.EnterTransition getEnterTransition(androidx.compose.material3.adaptive.layout.PaneScaffoldMotionScope);
     method public androidx.compose.animation.ExitTransition getExitTransition(androidx.compose.material3.adaptive.layout.PaneScaffoldMotionScope);
+    field public static final androidx.compose.material3.adaptive.layout.PaneMotion.Companion Companion;
+  }
+
+  public static final class PaneMotion.Companion {
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getAnimateBounds();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getEnterFromLeft();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getEnterFromLeftDelayed();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getEnterFromRight();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getEnterFromRightDelayed();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getEnterWithExpand();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getExitToLeft();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getExitToRight();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getExitWithShrink();
+    method public androidx.compose.material3.adaptive.layout.PaneMotion getNoMotion();
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion AnimateBounds;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion EnterFromLeft;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion EnterFromLeftDelayed;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion EnterFromRight;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion EnterFromRightDelayed;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion EnterWithExpand;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion ExitToLeft;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion ExitToRight;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion ExitWithShrink;
+    property public final androidx.compose.material3.adaptive.layout.PaneMotion NoMotion;
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class PaneMotionData {
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneMotionTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneMotionTest.kt
index 10d5ba2..a55e8ba 100644
--- a/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneMotionTest.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/layout/PaneMotionTest.kt
@@ -23,18 +23,18 @@
 import androidx.compose.animation.slideInHorizontally
 import androidx.compose.animation.slideOutHorizontally
 import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
-import androidx.compose.material3.adaptive.layout.DefaultPaneMotion.Companion.AnimateBounds
-import androidx.compose.material3.adaptive.layout.DefaultPaneMotion.Companion.EnterFromLeft
-import androidx.compose.material3.adaptive.layout.DefaultPaneMotion.Companion.EnterFromLeftDelayed
-import androidx.compose.material3.adaptive.layout.DefaultPaneMotion.Companion.EnterFromRight
-import androidx.compose.material3.adaptive.layout.DefaultPaneMotion.Companion.EnterFromRightDelayed
-import androidx.compose.material3.adaptive.layout.DefaultPaneMotion.Companion.EnterWithExpand
-import androidx.compose.material3.adaptive.layout.DefaultPaneMotion.Companion.ExitToLeft
-import androidx.compose.material3.adaptive.layout.DefaultPaneMotion.Companion.ExitToRight
-import androidx.compose.material3.adaptive.layout.DefaultPaneMotion.Companion.ExitWithShrink
-import androidx.compose.material3.adaptive.layout.DefaultPaneMotion.Companion.NoMotion
 import androidx.compose.material3.adaptive.layout.PaneAdaptedValue.Companion.Expanded
 import androidx.compose.material3.adaptive.layout.PaneAdaptedValue.Companion.Hidden
+import androidx.compose.material3.adaptive.layout.PaneMotion.Companion.AnimateBounds
+import androidx.compose.material3.adaptive.layout.PaneMotion.Companion.EnterFromLeft
+import androidx.compose.material3.adaptive.layout.PaneMotion.Companion.EnterFromLeftDelayed
+import androidx.compose.material3.adaptive.layout.PaneMotion.Companion.EnterFromRight
+import androidx.compose.material3.adaptive.layout.PaneMotion.Companion.EnterFromRightDelayed
+import androidx.compose.material3.adaptive.layout.PaneMotion.Companion.EnterWithExpand
+import androidx.compose.material3.adaptive.layout.PaneMotion.Companion.ExitToLeft
+import androidx.compose.material3.adaptive.layout.PaneMotion.Companion.ExitToRight
+import androidx.compose.material3.adaptive.layout.PaneMotion.Companion.ExitWithShrink
+import androidx.compose.material3.adaptive.layout.PaneMotion.Companion.NoMotion
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
@@ -79,7 +79,7 @@
         ExitWithShrink.assertTransitions(EnterTransition.None, mockExitWithShrinkTransition)
     }
 
-    private fun DefaultPaneMotion.assertTransitions(
+    private fun PaneMotion.assertTransitions(
         expectedEnterTransition: EnterTransition,
         expectedExitTransition: ExitTransition
     ) {
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/Pane.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/Pane.kt
index df65278..7059197 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/Pane.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/Pane.kt
@@ -43,7 +43,7 @@
     modifier: Modifier = Modifier,
     content: (@Composable AnimatedPaneScope.() -> Unit),
 ) {
-    val animatingBounds = paneMotion == DefaultPaneMotion.AnimateBounds
+    val animatingBounds = paneMotion == PaneMotion.AnimateBounds
     val motionProgress = { motionProgress }
     scaffoldStateTransition.AnimatedVisibility(
         visible = { value: T -> value[paneRole] != PaneAdaptedValue.Hidden },
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneMotion.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneMotion.kt
index 58798db..8d99c09 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneMotion.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneMotion.kt
@@ -97,7 +97,7 @@
  */
 @ExperimentalMaterial3AdaptiveApi
 class PaneMotionData internal constructor() {
-    var motion: PaneMotion = DefaultPaneMotion.NoMotion
+    var motion: PaneMotion = PaneMotion.NoMotion
         internal set
 
     var currentSize: IntSize = IntSize.Zero
@@ -119,8 +119,8 @@
         // Find the right edge offset of the rightmost pane that enters from its left
         paneMotionDataList.fastForEachReversed {
             if (
-                it.motion == DefaultPaneMotion.EnterFromLeft ||
-                    it.motion == DefaultPaneMotion.EnterFromLeftDelayed
+                it.motion == PaneMotion.EnterFromLeft ||
+                    it.motion == PaneMotion.EnterFromLeftDelayed
             ) {
                 return -it.targetPosition.x - it.targetSize.width
             }
@@ -134,8 +134,8 @@
         // Find the left edge offset of the leftmost pane that enters from its right
         paneMotionDataList.fastForEach {
             if (
-                it.motion == DefaultPaneMotion.EnterFromRight ||
-                    it.motion == DefaultPaneMotion.EnterFromRightDelayed
+                it.motion == PaneMotion.EnterFromRight ||
+                    it.motion == PaneMotion.EnterFromRightDelayed
             ) {
                 return scaffoldSize.width - it.targetPosition.x
             }
@@ -148,7 +148,7 @@
     get() {
         // Find the right edge offset of the rightmost pane that exits to its left
         paneMotionDataList.fastForEachReversed {
-            if (it.motion == DefaultPaneMotion.ExitToLeft) {
+            if (it.motion == PaneMotion.ExitToLeft) {
                 return -it.currentPosition.x - it.currentSize.width
             }
         }
@@ -160,7 +160,7 @@
     get() {
         // Find the left edge offset of the leftmost pane that exits to its right
         paneMotionDataList.fastForEach {
-            if (it.motion == DefaultPaneMotion.ExitToRight) {
+            if (it.motion == PaneMotion.ExitToRight) {
                 return scaffoldSize.width - it.currentPosition.x
             }
         }
@@ -175,66 +175,116 @@
 
     /** The [ExitTransition] of a pane under the given [PaneScaffoldMotionScope] */
     val PaneScaffoldMotionScope.exitTransition: ExitTransition
-}
 
-@ExperimentalMaterial3AdaptiveApi
-@JvmInline
-internal value class DefaultPaneMotion private constructor(val value: Int) : PaneMotion {
-    companion object {
-        val NoMotion = DefaultPaneMotion(0)
-        val AnimateBounds = DefaultPaneMotion(1)
-        val EnterFromLeft = DefaultPaneMotion(2)
-        val EnterFromRight = DefaultPaneMotion(3)
-        val EnterFromLeftDelayed = DefaultPaneMotion(4)
-        val EnterFromRightDelayed = DefaultPaneMotion(5)
-        val ExitToLeft = DefaultPaneMotion(6)
-        val ExitToRight = DefaultPaneMotion(7)
-        val EnterWithExpand = DefaultPaneMotion(8)
-        val ExitWithShrink = DefaultPaneMotion(9)
+    private abstract class DefaultImpl(val name: String) : PaneMotion {
+        override val PaneScaffoldMotionScope.enterTransition
+            get() = EnterTransition.None
+
+        override val PaneScaffoldMotionScope.exitTransition
+            get() = ExitTransition.None
+
+        override fun toString() = name
     }
 
-    override val PaneScaffoldMotionScope.enterTransition: EnterTransition
-        get() =
-            when (this@DefaultPaneMotion) {
-                EnterFromLeft ->
-                    slideInHorizontally(positionAnimationSpec) { slideInFromLeftOffset }
-                EnterFromRight ->
-                    slideInHorizontally(positionAnimationSpec) { slideInFromRightOffset }
-                EnterFromLeftDelayed ->
-                    slideInHorizontally(delayedPositionAnimationSpec) { slideInFromLeftOffset }
-                EnterFromRightDelayed ->
-                    slideInHorizontally(delayedPositionAnimationSpec) { slideInFromRightOffset }
-                // TODO(conradche): Figure out how to expand with position change
-                EnterWithExpand ->
-                    expandHorizontally(sizeAnimationSpec, Alignment.CenterHorizontally)
-                else -> EnterTransition.None
+    companion object {
+        /** The default pane motion that no animation will be performed. */
+        val NoMotion: PaneMotion = object : DefaultImpl("NoMotion") {}
+
+        /**
+         * The default pane motion that will animate panes bounds with the given animation specs
+         * during motion. Note that this should only be used when the associated pane is keeping
+         * showing during the motion.
+         */
+        val AnimateBounds: PaneMotion = object : DefaultImpl("AnimateBounds") {}
+
+        /**
+         * The default pane motion that will slide panes in from left. Note that this should only be
+         * used when the associated pane is entering - i.e. becoming visible from a hidden state.
+         */
+        val EnterFromLeft: PaneMotion =
+            object : DefaultImpl("EnterFromLeft") {
+                override val PaneScaffoldMotionScope.enterTransition
+                    get() = slideInHorizontally(positionAnimationSpec) { slideInFromLeftOffset }
             }
 
-    override val PaneScaffoldMotionScope.exitTransition: ExitTransition
-        get() =
-            when (this@DefaultPaneMotion) {
-                ExitToLeft -> slideOutHorizontally(positionAnimationSpec) { slideOutToLeftOffset }
-                ExitToRight -> slideOutHorizontally(positionAnimationSpec) { slideOutToRightOffset }
-                // TODO(conradche): Figure out how to shrink with position change
-                ExitWithShrink ->
-                    shrinkHorizontally(sizeAnimationSpec, Alignment.CenterHorizontally)
-                else -> ExitTransition.None
+        /**
+         * The default pane motion that will slide panes in from right. Note that this should only
+         * be used when the associated pane is entering - i.e. becoming visible from a hidden state.
+         */
+        val EnterFromRight: PaneMotion =
+            object : DefaultImpl("EnterFromRight") {
+                override val PaneScaffoldMotionScope.enterTransition
+                    get() = slideInHorizontally(positionAnimationSpec) { slideInFromRightOffset }
             }
 
-    override fun toString(): String =
-        when (this) {
-            NoMotion -> "NoMotion"
-            AnimateBounds -> "AnimateBounds"
-            EnterFromLeft -> "EnterFromLeft"
-            EnterFromRight -> "EnterFromRight"
-            EnterFromLeftDelayed -> "EnterFromLeftDelayed"
-            EnterFromRightDelayed -> "EnterFromRightDelayed"
-            ExitToLeft -> "ExitToLeft"
-            ExitToRight -> "ExitToRight"
-            EnterWithExpand -> "EnterWithExpand"
-            ExitWithShrink -> "ExitWithShrink"
-            else -> "Undefined($value)"
-        }
+        /**
+         * The default pane motion that will slide panes in from left with a delay, usually to avoid
+         * the interference of other exiting panes. Note that this should only be used when the
+         * associated pane is entering - i.e. becoming visible from a hidden state.
+         */
+        val EnterFromLeftDelayed: PaneMotion =
+            object : DefaultImpl("EnterFromLeftDelayed") {
+                override val PaneScaffoldMotionScope.enterTransition
+                    get() =
+                        slideInHorizontally(delayedPositionAnimationSpec) { slideInFromLeftOffset }
+            }
+
+        /**
+         * The default pane motion that will slide panes in from right with a delay, usually to
+         * avoid the interference of other exiting panes. Note that this should only be used when
+         * the associated pane is entering - i.e. becoming visible from a hidden state.
+         */
+        val EnterFromRightDelayed: PaneMotion =
+            object : DefaultImpl("EnterFromRightDelayed") {
+                override val PaneScaffoldMotionScope.enterTransition
+                    get() =
+                        slideInHorizontally(delayedPositionAnimationSpec) { slideInFromRightOffset }
+            }
+
+        /**
+         * The default pane motion that will slide panes out to left. Note that this should only be
+         * used when the associated pane is exiting - i.e. becoming hidden from a visible state.
+         */
+        val ExitToLeft: PaneMotion =
+            object : DefaultImpl("ExitToLeft") {
+                override val PaneScaffoldMotionScope.exitTransition
+                    get() = slideOutHorizontally(positionAnimationSpec) { slideOutToLeftOffset }
+            }
+
+        /**
+         * The default pane motion that will slide panes out to right. Note that this should only be
+         * used when the associated pane is exiting - i.e. becoming hidden from a visible state.
+         */
+        val ExitToRight: PaneMotion =
+            object : DefaultImpl("ExitToRight") {
+                override val PaneScaffoldMotionScope.exitTransition
+                    get() = slideOutHorizontally(positionAnimationSpec) { slideOutToRightOffset }
+            }
+
+        /**
+         * The default pane motion that will expand panes from a zero size. Note that this should
+         * only be used when the associated pane is entering - i.e. becoming visible from a hidden
+         * state.
+         */
+        val EnterWithExpand: PaneMotion =
+            object : DefaultImpl("EnterWithExpand") {
+                // TODO(conradchen): Expand with position change
+                override val PaneScaffoldMotionScope.enterTransition
+                    get() = expandHorizontally(sizeAnimationSpec, Alignment.CenterHorizontally)
+            }
+
+        /**
+         * The default pane motion that will shrink panes until it's gone. Note that this should
+         * only be used when the associated pane is exiting - i.e. becoming hidden from a visible
+         * state.
+         */
+        val ExitWithShrink: PaneMotion =
+            object : DefaultImpl("ExitWithShrink") {
+                // TODO(conradchen): Shrink with position change
+                override val PaneScaffoldMotionScope.exitTransition
+                    get() = shrinkHorizontally(sizeAnimationSpec, Alignment.CenterHorizontally)
+            }
+    }
 }
 
 @ExperimentalMaterial3AdaptiveApi
@@ -245,7 +295,7 @@
 ): List<PaneMotion> {
     val numOfPanes = paneOrder.size
     val paneStatus = Array(numOfPanes) { PaneMotionStatus.Hidden }
-    val paneMotions = MutableList<PaneMotion>(numOfPanes) { DefaultPaneMotion.NoMotion }
+    val paneMotions = MutableList(numOfPanes) { PaneMotion.NoMotion }
     var firstShownPaneIndex = numOfPanes
     var firstEnteringPaneIndex = numOfPanes
     var lastShownPaneIndex = -1
@@ -261,7 +311,7 @@
             PaneMotionStatus.Shown -> {
                 firstShownPaneIndex = min(firstShownPaneIndex, i)
                 lastShownPaneIndex = max(lastShownPaneIndex, i)
-                paneMotions[i] = DefaultPaneMotion.AnimateBounds
+                paneMotions[i] = PaneMotion.AnimateBounds
             }
             PaneMotionStatus.Entering -> {
                 firstEnteringPaneIndex = min(firstEnteringPaneIndex, i)
@@ -286,26 +336,26 @@
                     // No panes will interfere the motion on the right, exit to right.
                     hasPanesExitToRight = true
                     firstPaneExitToRightIndex = min(firstPaneExitToRightIndex, i)
-                    DefaultPaneMotion.ExitToRight
+                    PaneMotion.ExitToRight
                 } else if (!hasShownPanesOnLeft && !hasEnteringPanesOnLeft) {
                     // No panes will interfere the motion on the left, exit to left.
                     hasPanesExitToLeft = true
                     lastPaneExitToLeftIndex = max(lastPaneExitToLeftIndex, i)
-                    DefaultPaneMotion.ExitToLeft
+                    PaneMotion.ExitToLeft
                 } else if (!hasShownPanesOnRight) {
                     // Only showing panes can interfere the motion on the right, exit to right.
                     hasPanesExitToRight = true
                     firstPaneExitToRightIndex = min(firstPaneExitToRightIndex, i)
-                    DefaultPaneMotion.ExitToRight
+                    PaneMotion.ExitToRight
                 } else if (!hasShownPanesOnLeft) { // Only showing panes on left
                     // Only showing panes can interfere the motion on the left, exit to left.
                     hasPanesExitToLeft = true
                     lastPaneExitToLeftIndex = max(lastPaneExitToLeftIndex, i)
-                    DefaultPaneMotion.ExitToLeft
+                    PaneMotion.ExitToLeft
                 } else {
                     // Both sides has panes that keep being visible during transition, shrink to
                     // exit
-                    DefaultPaneMotion.ExitWithShrink
+                    PaneMotion.ExitWithShrink
                 }
         }
     }
@@ -326,20 +376,20 @@
             paneMotions[i] =
                 if (noBlockingPanesOnRight && !hasPanesExitToRight) {
                     // No panes will block the motion on the right, enter from right.
-                    DefaultPaneMotion.EnterFromRight
+                    PaneMotion.EnterFromRight
                 } else if (noBlockingPanesOnLeft && !hasPanesExitToLeft) {
                     // No panes will block the motion on the left, enter from left.
-                    DefaultPaneMotion.EnterFromLeft
+                    PaneMotion.EnterFromLeft
                 } else if (noBlockingPanesOnRight) {
                     // Only hiding panes can interfere the motion on the right, enter from right.
-                    DefaultPaneMotion.EnterFromRightDelayed
+                    PaneMotion.EnterFromRightDelayed
                 } else if (noBlockingPanesOnLeft) {
                     // Only hiding panes can interfere the motion on the left, enter from left.
-                    DefaultPaneMotion.EnterFromLeftDelayed
+                    PaneMotion.EnterFromLeftDelayed
                 } else {
                     // Both sides has panes that keep being visible during transition, expand to
                     // enter
-                    DefaultPaneMotion.EnterWithExpand
+                    PaneMotion.EnterWithExpand
                 }
         }
     }
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneMotion.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneMotion.kt
index 23a9c59..7924ad8 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneMotion.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneMotion.kt
@@ -235,11 +235,7 @@
     companion object {
         /** A default [ThreePaneMotion] instance that specifies no motions. */
         val NoMotion =
-            ThreePaneMotion(
-                DefaultPaneMotion.NoMotion,
-                DefaultPaneMotion.NoMotion,
-                DefaultPaneMotion.NoMotion
-            )
+            ThreePaneMotion(PaneMotion.NoMotion, PaneMotion.NoMotion, PaneMotion.NoMotion)
     }
 }
 
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScope.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScope.kt
index 8454908..15420c9 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScope.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScope.kt
@@ -51,7 +51,7 @@
     override val paneRole: ThreePaneScaffoldRole,
     scaffoldScope: ThreePaneScaffoldScope,
 ) : ThreePaneScaffoldPaneScope, ThreePaneScaffoldScope by scaffoldScope {
-    override var paneMotion: PaneMotion by mutableStateOf(DefaultPaneMotion.ExitToLeft)
+    override var paneMotion: PaneMotion by mutableStateOf(PaneMotion.ExitToLeft)
         private set
 
     fun updatePaneMotion(paneMotions: ThreePaneMotion) {
diff --git a/compose/material3/adaptive/samples/build.gradle b/compose/material3/adaptive/samples/build.gradle
index f42ef65..b9270f2 100644
--- a/compose/material3/adaptive/samples/build.gradle
+++ b/compose/material3/adaptive/samples/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 
 plugins {
@@ -54,6 +56,7 @@
     type = LibraryType.SAMPLES
     inceptionYear = "2023"
     description = "Contains the sample code for the AndroidX Compose Material Adaptive."
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/material3/material3-adaptive-navigation-suite/build.gradle b/compose/material3/material3-adaptive-navigation-suite/build.gradle
index 6d42780..8871853 100644
--- a/compose/material3/material3-adaptive-navigation-suite/build.gradle
+++ b/compose/material3/material3-adaptive-navigation-suite/build.gradle
@@ -22,6 +22,8 @@
  * modifying its settings.
  */
 
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import androidx.build.PlatformIdentifier
 
@@ -108,4 +110,5 @@
     legacyDisableKotlinStrictApiMode = true
     metalavaK2UastEnabled = false
     samples(project(":compose:material3:material3-adaptive-navigation-suite:material3-adaptive-navigation-suite-samples"))
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
diff --git a/compose/material3/material3-adaptive-navigation-suite/samples/build.gradle b/compose/material3/material3-adaptive-navigation-suite/samples/build.gradle
index 954f8fa..f7365c5 100644
--- a/compose/material3/material3-adaptive-navigation-suite/samples/build.gradle
+++ b/compose/material3/material3-adaptive-navigation-suite/samples/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 
 plugins {
@@ -54,6 +56,7 @@
     inceptionYear = "2023"
     description = "Contains the sample code for the AndroidX Compose Material Adaptive Navigation" +
                   " Suite."
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/material3/material3-common/build.gradle b/compose/material3/material3-common/build.gradle
index 7ec2dce..0572958 100644
--- a/compose/material3/material3-common/build.gradle
+++ b/compose/material3/material3-common/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import androidx.build.PlatformIdentifier
 import androidx.build.Publish
@@ -113,4 +115,5 @@
     legacyDisableKotlinStrictApiMode = true
     metalavaK2UastEnabled = false
     samples(project(":compose:material3:material3-common:material3-common-samples"))
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
diff --git a/compose/material3/material3-common/samples/build.gradle b/compose/material3/material3-common/samples/build.gradle
index 3036c4a..6c1bacf 100644
--- a/compose/material3/material3-common/samples/build.gradle
+++ b/compose/material3/material3-common/samples/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 
 plugins {
@@ -49,6 +51,7 @@
     type = LibraryType.SAMPLES
     inceptionYear = "2024"
     description = "Contains the sample code for the AndroidX Compose Material3-common components."
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/material3/material3-lint/src/main/java/androidx/compose/material3/lint/Material3IssueRegistry.kt b/compose/material3/material3-lint/src/main/java/androidx/compose/material3/lint/Material3IssueRegistry.kt
index 7891f7a..d95b4f1 100644
--- a/compose/material3/material3-lint/src/main/java/androidx/compose/material3/lint/Material3IssueRegistry.kt
+++ b/compose/material3/material3-lint/src/main/java/androidx/compose/material3/lint/Material3IssueRegistry.kt
@@ -23,7 +23,7 @@
 /** [IssueRegistry] containing Material3 specific lint issues. */
 class Material3IssueRegistry : IssueRegistry() {
     // Tests are run with this version. We ensure that with ApiLintVersionsTest
-    override val api = 14
+    override val api = 16
     override val minApi = CURRENT_API
     override val issues
         get() =
diff --git a/compose/material3/material3-window-size-class/build.gradle b/compose/material3/material3-window-size-class/build.gradle
index 04d992e..a943601 100644
--- a/compose/material3/material3-window-size-class/build.gradle
+++ b/compose/material3/material3-window-size-class/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import androidx.build.PlatformIdentifier
 
@@ -109,6 +111,7 @@
     legacyDisableKotlinStrictApiMode = true
     metalavaK2UastEnabled = false
     samples(project(":compose:material3:material3-window-size-class:material3-window-size-class-samples"))
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/material3/material3-window-size-class/samples/build.gradle b/compose/material3/material3-window-size-class/samples/build.gradle
index 1f9a9da..91b8ad3 100644
--- a/compose/material3/material3-window-size-class/samples/build.gradle
+++ b/compose/material3/material3-window-size-class/samples/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 
 plugins {
@@ -46,6 +48,7 @@
     type = LibraryType.SAMPLES
     inceptionYear = "2022"
     description = "Contains the sample code for the Material 3 Window Size Class APIs"
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/material3/material3/build.gradle b/compose/material3/material3/build.gradle
index 0d653a8..9f02e2f 100644
--- a/compose/material3/material3/build.gradle
+++ b/compose/material3/material3/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import androidx.build.PlatformIdentifier
 
@@ -133,6 +135,7 @@
     legacyDisableKotlinStrictApiMode = true
     metalavaK2UastEnabled = false
     samples(project(":compose:material3:material3:material3-samples"))
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 // Screenshot tests related setup
diff --git a/compose/material3/material3/samples/build.gradle b/compose/material3/material3/samples/build.gradle
index 107bcc8..1cfa87e 100644
--- a/compose/material3/material3/samples/build.gradle
+++ b/compose/material3/material3/samples/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 
 plugins {
@@ -57,6 +59,7 @@
     type = LibraryType.SAMPLES
     inceptionYear = "2021"
     description = "Contains the sample code for the AndroidX Compose Material You components."
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ScaffoldTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ScaffoldTest.kt
index 95d91c4..53dc2b9 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ScaffoldTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ScaffoldTest.kt
@@ -24,6 +24,7 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.requiredHeight
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.windowInsetsPadding
@@ -39,6 +40,7 @@
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.layout.positionInParent
 import androidx.compose.ui.layout.positionInRoot
+import androidx.compose.ui.layout.positionInWindow
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.semantics
@@ -151,7 +153,7 @@
                             .height(50.dp)
                             .background(color = Color.Red)
                             .onGloballyPositioned { positioned: LayoutCoordinates ->
-                                appbarPosition = positioned.positionInParent()
+                                appbarPosition = positioned.positionInWindow()
                                 appbarSize = positioned.size
                             }
                     )
@@ -164,7 +166,7 @@
                 Box(
                     Modifier.fillMaxSize().background(color = Color.Blue).onGloballyPositioned {
                         positioned: LayoutCoordinates ->
-                        contentPosition = positioned.positionInParent()
+                        contentPosition = positioned.positionInWindow()
                         contentSize = positioned.size
                     }
                 )
@@ -340,7 +342,7 @@
             Box(Modifier.requiredSize(10.dp, 40.dp)) {
                 Scaffold(
                     contentWindowInsets = WindowInsets(top = 5.dp, bottom = 3.dp),
-                    topBar = { Box(Modifier.requiredSize(0.dp)) }
+                    topBar = { Box(Modifier.requiredHeight(0.dp).fillMaxWidth()) }
                 ) { paddingValues ->
                     // top is like the collapsed top app bar (i.e. 0dp) + rounding error
                     assertDpIsWithinThreshold(
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt
index e6c27fd..31245c0 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.material3
 
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.asPaddingValues
@@ -32,13 +33,12 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.SubcomposeLayout
+import androidx.compose.ui.semantics.isTraversalGroup
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.traversalIndex
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.offset
-import androidx.compose.ui.util.fastForEach
-import androidx.compose.ui.util.fastMap
-import androidx.compose.ui.util.fastMapNotNull
-import androidx.compose.ui.util.fastMaxBy
 
 /**
  * <a href="https://m3.material.io/foundations/layout/understanding-layout/" class="external"
@@ -136,49 +136,67 @@
     contentWindowInsets: WindowInsets,
     bottomBar: @Composable () -> Unit
 ) {
-    SubcomposeLayout { constraints ->
+    SubcomposeLayout(modifier = Modifier.semantics { isTraversalGroup = true }) { constraints ->
         val layoutWidth = constraints.maxWidth
         val layoutHeight = constraints.maxHeight
 
         val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0)
 
-        val topBarPlaceables =
-            subcompose(ScaffoldLayoutContent.TopBar, topBar).fastMap {
-                it.measure(looseConstraints)
-            }
+        // respect only bottom and horizontal for snackbar and fab
+        val leftInset = contentWindowInsets.getLeft(this@SubcomposeLayout, layoutDirection)
+        val rightInset = contentWindowInsets.getRight(this@SubcomposeLayout, layoutDirection)
+        val bottomInset = contentWindowInsets.getBottom(this@SubcomposeLayout)
 
-        val topBarHeight = topBarPlaceables.fastMaxBy { it.height }?.height ?: 0
+        val topBarPlaceable =
+            subcompose(ScaffoldLayoutContent.TopBar) {
+                    Box(
+                        modifier =
+                            Modifier.semantics {
+                                isTraversalGroup = true
+                                traversalIndex = 0f
+                            },
+                    ) {
+                        topBar()
+                    }
+                }
+                .first()
+                .measure(looseConstraints)
 
-        val snackbarPlaceables =
-            subcompose(ScaffoldLayoutContent.Snackbar, snackbar).fastMap {
-                // respect only bottom and horizontal for snackbar and fab
-                val leftInset = contentWindowInsets.getLeft(this@SubcomposeLayout, layoutDirection)
-                val rightInset =
-                    contentWindowInsets.getRight(this@SubcomposeLayout, layoutDirection)
-                val bottomInset = contentWindowInsets.getBottom(this@SubcomposeLayout)
-                // offset the snackbar constraints by the insets values
-                it.measure(looseConstraints.offset(-leftInset - rightInset, -bottomInset))
-            }
+        val snackbarPlaceable =
+            subcompose(ScaffoldLayoutContent.Snackbar) {
+                    Box(
+                        modifier =
+                            Modifier.semantics {
+                                isTraversalGroup = true
+                                traversalIndex = 4f
+                            },
+                    ) {
+                        snackbar()
+                    }
+                }
+                .first()
+                .measure(looseConstraints.offset(-leftInset - rightInset, -bottomInset))
 
-        val snackbarHeight = snackbarPlaceables.fastMaxBy { it.height }?.height ?: 0
-        val snackbarWidth = snackbarPlaceables.fastMaxBy { it.width }?.width ?: 0
+        val fabPlaceable =
+            subcompose(ScaffoldLayoutContent.Fab) {
+                    Box(
+                        modifier =
+                            Modifier.semantics {
+                                isTraversalGroup = true
+                                traversalIndex = 2f
+                            }
+                    ) {
+                        fab()
+                    }
+                }
+                .first()
+                .measure(looseConstraints.offset(-leftInset - rightInset, -bottomInset))
 
-        val fabPlaceables =
-            subcompose(ScaffoldLayoutContent.Fab, fab).fastMapNotNull { measurable ->
-                // respect only bottom and horizontal for snackbar and fab
-                val leftInset = contentWindowInsets.getLeft(this@SubcomposeLayout, layoutDirection)
-                val rightInset =
-                    contentWindowInsets.getRight(this@SubcomposeLayout, layoutDirection)
-                val bottomInset = contentWindowInsets.getBottom(this@SubcomposeLayout)
-                measurable
-                    .measure(looseConstraints.offset(-leftInset - rightInset, -bottomInset))
-                    .takeIf { it.height != 0 && it.width != 0 }
-            }
-
+        val isFabEmpty = fabPlaceable.width == 0 && fabPlaceable.height == 0
         val fabPlacement =
-            if (fabPlaceables.isNotEmpty()) {
-                val fabWidth = fabPlaceables.fastMaxBy { it.width }!!.width
-                val fabHeight = fabPlaceables.fastMaxBy { it.height }!!.height
+            if (!isFabEmpty) {
+                val fabWidth = fabPlaceable.width
+                val fabHeight = fabPlaceable.height
                 // FAB distance from the left of the layout, taking into account LTR / RTL
                 val fabLeftOffset =
                     when (fabPosition) {
@@ -205,50 +223,63 @@
                 null
             }
 
-        val bottomBarPlaceables =
-            subcompose(ScaffoldLayoutContent.BottomBar) { bottomBar() }
-                .fastMap { it.measure(looseConstraints) }
+        val bottomBarPlaceable =
+            subcompose(ScaffoldLayoutContent.BottomBar) {
+                    Box(
+                        modifier =
+                            Modifier.semantics {
+                                isTraversalGroup = true
+                                traversalIndex = 1f
+                            }
+                    ) {
+                        bottomBar()
+                    }
+                }
+                .first()
+                .measure(looseConstraints)
 
-        val bottomBarHeight = bottomBarPlaceables.fastMaxBy { it.height }?.height
+        val isBottomBarEmpty = bottomBarPlaceable.width == 0 && bottomBarPlaceable.height == 0
+
         val fabOffsetFromBottom =
             fabPlacement?.let {
-                if (bottomBarHeight == null || fabPosition == FabPosition.EndOverlay) {
+                if (isBottomBarEmpty || fabPosition == FabPosition.EndOverlay) {
                     it.height +
                         FabSpacing.roundToPx() +
                         contentWindowInsets.getBottom(this@SubcomposeLayout)
                 } else {
                     // Total height is the bottom bar height + the FAB height + the padding
                     // between the FAB and bottom bar
-                    bottomBarHeight + it.height + FabSpacing.roundToPx()
+                    bottomBarPlaceable.height + it.height + FabSpacing.roundToPx()
                 }
             }
 
+        val snackbarHeight = snackbarPlaceable.height
         val snackbarOffsetFromBottom =
             if (snackbarHeight != 0) {
                 snackbarHeight +
                     (fabOffsetFromBottom
-                        ?: bottomBarHeight
+                        ?: bottomBarPlaceable.height.takeIf { !isBottomBarEmpty }
                         ?: contentWindowInsets.getBottom(this@SubcomposeLayout))
             } else {
                 0
             }
 
-        val bodyContentPlaceables =
+        val bodyContentPlaceable =
             subcompose(ScaffoldLayoutContent.MainContent) {
                     val insets = contentWindowInsets.asPaddingValues(this@SubcomposeLayout)
                     val innerPadding =
                         PaddingValues(
                             top =
-                                if (topBarPlaceables.isEmpty()) {
+                                if (topBarPlaceable.width == 0 && topBarPlaceable.height == 0) {
                                     insets.calculateTopPadding()
                                 } else {
-                                    topBarHeight.toDp()
+                                    topBarPlaceable.height.toDp()
                                 },
                             bottom =
-                                if (bottomBarPlaceables.isEmpty() || bottomBarHeight == null) {
+                                if (isBottomBarEmpty) {
                                     insets.calculateBottomPadding()
                                 } else {
-                                    bottomBarHeight.toDp()
+                                    bottomBarPlaceable.height.toDp()
                                 },
                             start =
                                 insets.calculateStartPadding(
@@ -257,29 +288,33 @@
                             end =
                                 insets.calculateEndPadding((this@SubcomposeLayout).layoutDirection)
                         )
-                    content(innerPadding)
+                    Box(
+                        modifier =
+                            Modifier.semantics {
+                                isTraversalGroup = true
+                                traversalIndex = 3f
+                            }
+                    ) {
+                        content(innerPadding)
+                    }
                 }
-                .fastMap { it.measure(looseConstraints) }
+                .first()
+                .measure(looseConstraints)
 
         layout(layoutWidth, layoutHeight) {
             // Placing to control drawing order to match default elevation of each placeable
-
-            bodyContentPlaceables.fastForEach { it.place(0, 0) }
-            topBarPlaceables.fastForEach { it.place(0, 0) }
-            snackbarPlaceables.fastForEach {
-                it.place(
-                    (layoutWidth - snackbarWidth) / 2 +
-                        contentWindowInsets.getLeft(this@SubcomposeLayout, layoutDirection),
-                    layoutHeight - snackbarOffsetFromBottom
-                )
-            }
+            bodyContentPlaceable.place(0, 0)
+            topBarPlaceable.place(0, 0)
+            snackbarPlaceable.place(
+                (layoutWidth - snackbarPlaceable.width) / 2 +
+                    contentWindowInsets.getLeft(this@SubcomposeLayout, layoutDirection),
+                layoutHeight - snackbarOffsetFromBottom
+            )
             // The bottom bar is always at the bottom of the layout
-            bottomBarPlaceables.fastForEach { it.place(0, layoutHeight - (bottomBarHeight ?: 0)) }
+            bottomBarPlaceable.place(0, layoutHeight - (bottomBarPlaceable.height))
             // Explicitly not using placeRelative here as `leftOffset` already accounts for RTL
             fabPlacement?.let { placement ->
-                fabPlaceables.fastForEach {
-                    it.place(placement.left, layoutHeight - fabOffsetFromBottom!!)
-                }
+                fabPlaceable.place(placement.left, layoutHeight - fabOffsetFromBottom!!)
             }
         }
     }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Shapes.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Shapes.kt
index 7619c29..e37af95 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Shapes.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Shapes.kt
@@ -78,6 +78,7 @@
  *   [Shapes.extraLarge] and smaller than [CircleShape].
  */
 // TODO: Update new shape descriptions to list what components leverage them by default.
+// TODO(b/368578382): Update 'increased' variant kdocs to reference design documentation.
 @Immutable
 class Shapes
 @ExperimentalMaterial3ExpressiveApi
@@ -293,6 +294,7 @@
     /** An extra extra large (XXL) sized corner shape */
     val ExtraExtraLarge: CornerBasedShape = RoundedCornerShape(48.dp)
 
+    // TODO(b/368578382): Update 'increased' variant kdocs to reference design documentation.
     /** A non-rounded corner size */
     internal val CornerNone: CornerSize = CornerSize(0.dp)
 
diff --git a/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/TimePicker.commonStubs.kt b/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/TimePicker.commonStubs.kt
index 8b28f7b..8d6d9fc 100644
--- a/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/TimePicker.commonStubs.kt
+++ b/compose/material3/material3/src/commonStubsMain/kotlin/androidx/compose/material3/TimePicker.commonStubs.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT")
+
 package androidx.compose.material3
 
 @OptIn(ExperimentalMaterial3Api::class)
diff --git a/compose/runtime/runtime-lint/build.gradle b/compose/runtime/runtime-lint/build.gradle
index d0426fb..c4f542e 100644
--- a/compose/runtime/runtime-lint/build.gradle
+++ b/compose/runtime/runtime-lint/build.gradle
@@ -32,14 +32,14 @@
 BundleInsideHelper.forInsideLintJar(project)
 
 dependencies {
-    compileOnly(libs.androidLintApi)
+    compileOnly(libs.androidLintPrevApi)
     compileOnly(libs.kotlinStdlib)
     bundleInside(project(":compose:lint:common"))
 
     testImplementation(project(":compose:lint:common-test"))
     testImplementation(libs.kotlinStdlib)
-    testImplementation(libs.androidLint)
-    testImplementation(libs.androidLintTests)
+    testImplementation(libs.androidLintPrev)
+    testImplementation(libs.androidLintPrevTests)
     testImplementation(libs.junit)
     testImplementation(libs.truth)
 }
diff --git a/compose/runtime/runtime-livedata/build.gradle b/compose/runtime/runtime-livedata/build.gradle
index d42b4b5..0b18abc 100644
--- a/compose/runtime/runtime-livedata/build.gradle
+++ b/compose/runtime/runtime-livedata/build.gradle
@@ -21,8 +21,9 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
@@ -54,6 +55,7 @@
     description = "Compose integration with LiveData"
     legacyDisableKotlinStrictApiMode = true
     samples(project(":compose:runtime:runtime-livedata:runtime-livedata-samples"))
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/runtime/runtime-livedata/samples/build.gradle b/compose/runtime/runtime-livedata/samples/build.gradle
index 12e5ab5..1a2df3a 100644
--- a/compose/runtime/runtime-livedata/samples/build.gradle
+++ b/compose/runtime/runtime-livedata/samples/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 
 plugins {
@@ -44,6 +46,7 @@
     type = LibraryType.SAMPLES
     inceptionYear = "2019"
     description = "Contains the sample code for the Androidx Compose UI Livedata Interop System"
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/runtime/runtime-rxjava2/build.gradle b/compose/runtime/runtime-rxjava2/build.gradle
index 1865745..6b9c97f 100644
--- a/compose/runtime/runtime-rxjava2/build.gradle
+++ b/compose/runtime/runtime-rxjava2/build.gradle
@@ -21,8 +21,9 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
@@ -48,6 +49,7 @@
 androidx {
     name = "Compose RxJava 2 integration"
     type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
     inceptionYear = "2020"
     description = "Compose integration with RxJava 2"
     legacyDisableKotlinStrictApiMode = true
diff --git a/compose/runtime/runtime-rxjava2/samples/build.gradle b/compose/runtime/runtime-rxjava2/samples/build.gradle
index ba7d0b8..f7a97aa 100644
--- a/compose/runtime/runtime-rxjava2/samples/build.gradle
+++ b/compose/runtime/runtime-rxjava2/samples/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 
 plugins {
@@ -44,6 +46,7 @@
     type = LibraryType.SAMPLES
     inceptionYear = "2019"
     description = "Contains the sample code for the Androidx Compose RxJava 2 Integration System"
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/runtime/runtime-rxjava3/build.gradle b/compose/runtime/runtime-rxjava3/build.gradle
index af420bf..c06cd10 100644
--- a/compose/runtime/runtime-rxjava3/build.gradle
+++ b/compose/runtime/runtime-rxjava3/build.gradle
@@ -21,8 +21,9 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
@@ -48,6 +49,7 @@
 androidx {
     name = "Compose RxJava 3 integration"
     type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
     inceptionYear = "2020"
     description = "Compose integration with RxJava 3"
     legacyDisableKotlinStrictApiMode = true
diff --git a/compose/runtime/runtime-rxjava3/samples/build.gradle b/compose/runtime/runtime-rxjava3/samples/build.gradle
index 9906d22..9d6b156 100644
--- a/compose/runtime/runtime-rxjava3/samples/build.gradle
+++ b/compose/runtime/runtime-rxjava3/samples/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 
 plugins {
@@ -44,6 +46,7 @@
     type = LibraryType.SAMPLES
     inceptionYear = "2020"
     description = "Contains the sample code for the Androidx Compose RxJava 3 Integration System"
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/runtime/runtime-saveable-lint/src/main/java/androidx/compose/runtime/saveable/lint/RuntimeSaveableIssueRegistry.kt b/compose/runtime/runtime-saveable-lint/src/main/java/androidx/compose/runtime/saveable/lint/RuntimeSaveableIssueRegistry.kt
index fb64c4b..fcf1aff 100644
--- a/compose/runtime/runtime-saveable-lint/src/main/java/androidx/compose/runtime/saveable/lint/RuntimeSaveableIssueRegistry.kt
+++ b/compose/runtime/runtime-saveable-lint/src/main/java/androidx/compose/runtime/saveable/lint/RuntimeSaveableIssueRegistry.kt
@@ -25,7 +25,7 @@
 /** [IssueRegistry] containing runtime-saveable specific lint issues. */
 class RuntimeSaveableIssueRegistry : IssueRegistry() {
     // Tests are run with this version. We ensure that with ApiLintVersionsTest
-    override val api = 14
+    override val api = 16
     override val minApi = CURRENT_API
     override val issues
         get() = listOf(RememberSaveableDetector.RememberSaveableSaverParameter)
diff --git a/compose/runtime/runtime-saveable/build.gradle b/compose/runtime/runtime-saveable/build.gradle
index bccb80c..c1c5fa0 100644
--- a/compose/runtime/runtime-saveable/build.gradle
+++ b/compose/runtime/runtime-saveable/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import androidx.build.PlatformIdentifier
 
@@ -113,6 +115,7 @@
     legacyDisableKotlinStrictApiMode = true
     metalavaK2UastEnabled = false
     samples(project(":compose:runtime:runtime-saveable:runtime-saveable-samples"))
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/runtime/runtime-saveable/samples/build.gradle b/compose/runtime/runtime-saveable/samples/build.gradle
index dec1229a..89ac64c 100644
--- a/compose/runtime/runtime-saveable/samples/build.gradle
+++ b/compose/runtime/runtime-saveable/samples/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 
 plugins {
@@ -47,6 +49,7 @@
     type = LibraryType.SAMPLES
     inceptionYear = "2019"
     description = "Contains the sample code for the Androidx Compose Saved Instance State System"
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/runtime/runtime-tracing/build.gradle b/compose/runtime/runtime-tracing/build.gradle
index d6c37d5..d436226 100644
--- a/compose/runtime/runtime-tracing/build.gradle
+++ b/compose/runtime/runtime-tracing/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 
 plugins {
@@ -47,6 +49,7 @@
 androidx {
     name = "Compose Runtime: Tracing"
     type = LibraryType.PUBLISHED_LIBRARY_ONLY_USED_BY_KOTLIN_CONSUMERS
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
     inceptionYear = "2022"
     description = "Additional tracing in Compose"
     legacyDisableKotlinStrictApiMode = true
diff --git a/compose/runtime/runtime/build.gradle b/compose/runtime/runtime/build.gradle
index 9778e12..fb2f039 100644
--- a/compose/runtime/runtime/build.gradle
+++ b/compose/runtime/runtime/build.gradle
@@ -22,6 +22,8 @@
  * modifying its settings.
  */
 
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import androidx.build.PlatformIdentifier
 
@@ -147,4 +149,5 @@
     legacyDisableKotlinStrictApiMode = true
     metalavaK2UastEnabled = false
     samples(project(":compose:runtime:runtime:runtime-samples"))
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
diff --git a/compose/runtime/runtime/lint-baseline.xml b/compose/runtime/runtime/lint-baseline.xml
index 520e2756..9046383 100644
--- a/compose/runtime/runtime/lint-baseline.xml
+++ b/compose/runtime/runtime/lint-baseline.xml
@@ -1,23 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.7.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.7.0-alpha02)" variant="all" version="8.7.0-alpha02">
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="                val composition = compositionsToRetry.removeLastKt()"
-        errorLine2="                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="    get(key)?.let { list -> list.removeFirstKt().also { if (list.isEmpty()) remove(key) } }"
-        errorLine2="                            ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt"/>
-    </issue>
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
 
     <issue
         id="ExperimentalPropertyAnnotation"
diff --git a/compose/runtime/runtime/samples/build.gradle b/compose/runtime/runtime/samples/build.gradle
index 11be910..5ac8fb5 100644
--- a/compose/runtime/runtime/samples/build.gradle
+++ b/compose/runtime/runtime/samples/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 
 plugins {
@@ -46,6 +48,7 @@
     type = LibraryType.SAMPLES
     inceptionYear = "2019"
     description = "Contains the sample code for the Compose runtime"
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/TestOnly.jvm.kt b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/TestOnly.jvm.kt
index 6607bb8..1caf802 100644
--- a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/TestOnly.jvm.kt
+++ b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/TestOnly.jvm.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT")
+
 package androidx.compose.runtime
 
 internal actual typealias TestOnly = org.jetbrains.annotations.TestOnly
diff --git a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/Atomic.jvm.kt b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/Atomic.jvm.kt
index e1c38d0..7b6ce57 100644
--- a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/Atomic.jvm.kt
+++ b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/Atomic.jvm.kt
@@ -29,5 +29,13 @@
 
     override fun toShort(): Short = toInt().toShort()
 
+    @Deprecated(
+        "Direct conversion to Char is deprecated. Use toInt().toChar() or Char " +
+            "constructor instead.\nIf you override toChar() function in your Number inheritor, " +
+            "it's recommended to gradually deprecate the overriding function and then " +
+            "remove it.\nSee https://youtrack.jetbrains.com/issue/KT-46465 for details about " +
+            "the migration",
+        replaceWith = ReplaceWith("this.toInt().toChar()")
+    )
     override fun toChar(): Char = toInt().toChar()
 }
diff --git a/compose/ui/ui-geometry/build.gradle b/compose/ui/ui-geometry/build.gradle
index 9a0e69b..552d9fb 100644
--- a/compose/ui/ui-geometry/build.gradle
+++ b/compose/ui/ui-geometry/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import androidx.build.PlatformIdentifier
 
@@ -98,6 +100,7 @@
     description = "Compose classes related to dimensions without units"
     legacyDisableKotlinStrictApiMode = true
     metalavaK2UastEnabled = false
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/ui/ui-graphics-lint/src/main/java/androidx/compose/ui/graphics/lint/UiGraphicsIssueRegistry.kt b/compose/ui/ui-graphics-lint/src/main/java/androidx/compose/ui/graphics/lint/UiGraphicsIssueRegistry.kt
index 7d14531..b36e962 100644
--- a/compose/ui/ui-graphics-lint/src/main/java/androidx/compose/ui/graphics/lint/UiGraphicsIssueRegistry.kt
+++ b/compose/ui/ui-graphics-lint/src/main/java/androidx/compose/ui/graphics/lint/UiGraphicsIssueRegistry.kt
@@ -23,7 +23,7 @@
 /** [IssueRegistry] containing Compose UI graphics specific lint issues. */
 class UiGraphicsIssueRegistry : IssueRegistry() {
     // Tests are run with this version. We ensure that with ApiLintVersionsTest
-    override val api = 14
+    override val api = 16
     override val minApi = CURRENT_API
     override val issues
         get() = listOf(ColorDetector.MissingColorAlphaChannel, ColorDetector.InvalidColorHexValue)
diff --git a/compose/ui/ui-graphics/build.gradle b/compose/ui/ui-graphics/build.gradle
index f778f2a..75bba20 100644
--- a/compose/ui/ui-graphics/build.gradle
+++ b/compose/ui/ui-graphics/build.gradle
@@ -22,6 +22,8 @@
  * modifying its settings.
  */
 
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import androidx.build.PlatformIdentifier
 
@@ -117,6 +119,7 @@
     legacyDisableKotlinStrictApiMode = true
     metalavaK2UastEnabled = false
     samples(project(":compose:ui:ui-graphics:ui-graphics-samples"))
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 androidxCompose {
diff --git a/compose/ui/ui-graphics/samples/build.gradle b/compose/ui/ui-graphics/samples/build.gradle
index 927431d..99fb0f8 100644
--- a/compose/ui/ui-graphics/samples/build.gradle
+++ b/compose/ui/ui-graphics/samples/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 
 plugins {
@@ -47,6 +49,7 @@
     type = LibraryType.SAMPLES
     inceptionYear = "2019"
     description = "Contains the sample code for the Androidx Compose UI Graphics Components"
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/ui/ui-inspection/lint-baseline.xml b/compose/ui/ui-inspection/lint-baseline.xml
index a64ccd3..0f5d84e 100644
--- a/compose/ui/ui-inspection/lint-baseline.xml
+++ b/compose/ui/ui-inspection/lint-baseline.xml
@@ -1,14 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.7.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.7.0-alpha02)" variant="all" version="8.7.0-alpha02">
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="                val node = stack.removeLastKt()"
-        errorLine2="                           ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt"/>
-    </issue>
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
 
     <issue
         id="PrimitiveInCollection"
diff --git a/compose/ui/ui-lint/build.gradle b/compose/ui/ui-lint/build.gradle
index acb5164..23c978c 100644
--- a/compose/ui/ui-lint/build.gradle
+++ b/compose/ui/ui-lint/build.gradle
@@ -32,15 +32,15 @@
 BundleInsideHelper.forInsideLintJar(project)
 
 dependencies {
-    compileOnly(libs.androidLintApi)
+    compileOnly(libs.androidLintPrevApi)
     compileOnly(libs.kotlinStdlib)
 
     bundleInside(project(":compose:lint:common"))
 
     testImplementation(project(":compose:lint:common-test"))
     testImplementation(libs.kotlinStdlib)
-    testImplementation(libs.androidLint)
-    testImplementation(libs.androidLintTests)
+    testImplementation(libs.androidLintPrev)
+    testImplementation(libs.androidLintPrevTests)
     testImplementation(libs.junit)
     testImplementation(libs.truth)
 }
diff --git a/compose/ui/ui-test-junit4/build.gradle b/compose/ui/ui-test-junit4/build.gradle
index 7634a56..4885b12 100644
--- a/compose/ui/ui-test-junit4/build.gradle
+++ b/compose/ui/ui-test-junit4/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import androidx.build.PlatformIdentifier
 
@@ -125,4 +127,5 @@
     description = "Compose testing integration with JUnit4"
     legacyDisableKotlinStrictApiMode = true
     metalavaK2UastEnabled = false
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
diff --git a/compose/ui/ui-test-manifest-lint/src/main/java/androidx/compose/ui/test/manifest/lint/TestManifestIssueRegistry.kt b/compose/ui/ui-test-manifest-lint/src/main/java/androidx/compose/ui/test/manifest/lint/TestManifestIssueRegistry.kt
index 638143d..86c960f 100644
--- a/compose/ui/ui-test-manifest-lint/src/main/java/androidx/compose/ui/test/manifest/lint/TestManifestIssueRegistry.kt
+++ b/compose/ui/ui-test-manifest-lint/src/main/java/androidx/compose/ui/test/manifest/lint/TestManifestIssueRegistry.kt
@@ -21,7 +21,7 @@
 import com.android.tools.lint.detector.api.CURRENT_API
 
 class TestManifestIssueRegistry : IssueRegistry() {
-    override val api = 14
+    override val api = 16
     override val minApi = CURRENT_API
     override val issues
         get() = listOf(GradleDebugConfigurationDetector.ISSUE)
diff --git a/compose/ui/ui-test/build.gradle b/compose/ui/ui-test/build.gradle
index 653f362..0843ab7 100644
--- a/compose/ui/ui-test/build.gradle
+++ b/compose/ui/ui-test/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import androidx.build.PlatformIdentifier
 
@@ -160,4 +162,5 @@
     legacyDisableKotlinStrictApiMode = true
     metalavaK2UastEnabled = false
     samples(project(":compose:ui:ui-test:ui-test-samples"))
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
diff --git a/compose/ui/ui-test/lint-baseline.xml b/compose/ui/ui-test/lint-baseline.xml
index 0947205..a091357 100644
--- a/compose/ui/ui-test/lint-baseline.xml
+++ b/compose/ui/ui-test/lint-baseline.xml
@@ -1,23 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.7.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.7.0-alpha02)" variant="all" version="8.7.0-alpha02">
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="        return mutableListOf&lt;E>().also { result -> repeat(n) { result.add(removeFirstKt()) } }"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidUnitTest/kotlin/androidx/compose/ui/test/inputdispatcher/KeyAndMouseEventsTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="        return mutableListOf&lt;E>().also { result -> repeat(n) { result.add(removeFirstKt()) } }"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidUnitTest/kotlin/androidx/compose/ui/test/inputdispatcher/MouseEventsTest.kt"/>
-    </issue>
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
 
     <issue
         id="BanThreadSleep"
diff --git a/compose/ui/ui-test/samples/build.gradle b/compose/ui/ui-test/samples/build.gradle
index 9810d6f..f43e31c 100644
--- a/compose/ui/ui-test/samples/build.gradle
+++ b/compose/ui/ui-test/samples/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 
 plugins {
@@ -51,6 +53,7 @@
     type = LibraryType.SAMPLES
     inceptionYear = "2022"
     description = "Contains samples for AndroidX Compose Testing."
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/IsDisplayedTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/IsDisplayedTest.kt
index 541555f..4311d59 100644
--- a/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/IsDisplayedTest.kt
+++ b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/IsDisplayedTest.kt
@@ -84,10 +84,10 @@
             Box(
                 modifier =
                     with(Modifier) { width?.let { requiredWidth(it) } ?: fillMaxWidth() }
-                        .then(
-                            with(Modifier) { height?.let { requiredHeight(it) } ?: fillMaxHeight() }
-                        )
-                        .background(colors[i % colors.size])
+                        .let {
+                            with(it) { height?.let { requiredHeight(it) } ?: fillMaxHeight() }
+                                .background(colors[i % colors.size])
+                        }
             )
         }
     }
diff --git a/compose/ui/ui-text-google-fonts/build.gradle b/compose/ui/ui-text-google-fonts/build.gradle
index 5e65e32..917f32e 100644
--- a/compose/ui/ui-text-google-fonts/build.gradle
+++ b/compose/ui/ui-text-google-fonts/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
@@ -52,6 +54,7 @@
     inceptionYear = "2022"
     description = "Compose Downloadable Fonts integration for Google Fonts"
     legacyDisableKotlinStrictApiMode = true
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/ui/ui-text-lint/src/main/java/androidx/compose/ui/text/lint/UiTextIssueRegistry.kt b/compose/ui/ui-text-lint/src/main/java/androidx/compose/ui/text/lint/UiTextIssueRegistry.kt
index f1e728d..68e760f 100644
--- a/compose/ui/ui-text-lint/src/main/java/androidx/compose/ui/text/lint/UiTextIssueRegistry.kt
+++ b/compose/ui/ui-text-lint/src/main/java/androidx/compose/ui/text/lint/UiTextIssueRegistry.kt
@@ -22,7 +22,7 @@
 
 /** [IssueRegistry] containing Compose ui-text specific lint issues. */
 class UiTextIssueRegistry : IssueRegistry() {
-    override val api = 14
+    override val api = 16
     override val minApi = CURRENT_API
     override val issues
         get() = listOf(LocaleInvalidLanguageTagDetector.InvalidLanguageTagDelimiter)
diff --git a/compose/ui/ui-text-lint/src/test/java/androidx/compose/ui/text/lint/ApiLintVersionsTest.kt b/compose/ui/ui-text-lint/src/test/java/androidx/compose/ui/text/lint/ApiLintVersionsTest.kt
index de4db2a..0774160 100644
--- a/compose/ui/ui-text-lint/src/test/java/androidx/compose/ui/text/lint/ApiLintVersionsTest.kt
+++ b/compose/ui/ui-text-lint/src/test/java/androidx/compose/ui/text/lint/ApiLintVersionsTest.kt
@@ -28,6 +28,6 @@
 
         val registry = UiTextIssueRegistry()
         assertThat(registry.api).isEqualTo(CURRENT_API)
-        assertThat(registry.minApi).isEqualTo(14)
+        assertThat(registry.minApi).isEqualTo(16)
     }
 }
diff --git a/compose/ui/ui-text/build.gradle b/compose/ui/ui-text/build.gradle
index 2dd3ada..8e574c8 100644
--- a/compose/ui/ui-text/build.gradle
+++ b/compose/ui/ui-text/build.gradle
@@ -22,6 +22,8 @@
  * modifying its settings.
  */
 
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import androidx.build.PlatformIdentifier
 
@@ -143,6 +145,7 @@
     legacyDisableKotlinStrictApiMode = true
     metalavaK2UastEnabled = false
     samples(project(":compose:ui:ui-text:ui-text-samples"))
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/ui/ui-text/samples/build.gradle b/compose/ui/ui-text/samples/build.gradle
index f19e8a0..b6778fb 100644
--- a/compose/ui/ui-text/samples/build.gradle
+++ b/compose/ui/ui-text/samples/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 
 plugins {
@@ -48,6 +50,7 @@
     type = LibraryType.SAMPLES
     inceptionYear = "2019"
     description = "Contains sample code for the Androidx Compose UI Text Core APIs and Utilities"
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/AndroidParagraphTest.kt b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/AndroidParagraphTest.kt
index 074a435..82e26eb 100644
--- a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/AndroidParagraphTest.kt
+++ b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/AndroidParagraphTest.kt
@@ -1313,8 +1313,8 @@
         }
     }
 
-    // Experimentally verified that middle and start ellipsis don't work correctly on API 21
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP_MR1)
+    // Experimentally verified that start ellipsis doesn't work same way on API 22
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
     @Test
     fun testEllipsis_withMaxLinesOne_doesStartEllipsis() {
         with(defaultDensity) {
@@ -1336,7 +1336,7 @@
         }
     }
 
-    // Experimentally verified that middle and start ellipsis don't work correctly on API 21
+    // Experimentally verified that middle ellipsis doesn't work same way on API 21
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP_MR1)
     @Test
     fun testEllipsis_withMaxLinesOne_doesMiddleEllipsis() {
diff --git a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/ParagraphIntegrationTest.kt b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/ParagraphIntegrationTest.kt
index e12b98e..9359de6 100644
--- a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/ParagraphIntegrationTest.kt
+++ b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/ParagraphIntegrationTest.kt
@@ -15,6 +15,7 @@
  */
 package androidx.compose.ui.text
 
+import android.os.Build
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.geometry.Size
@@ -2935,6 +2936,52 @@
     }
 
     @Test
+    fun getLineStartEllipsisCount() {
+        val text = "aaaaabbbbbccccc"
+        val paragraph =
+            simpleParagraph(
+                text = text,
+                style = TextStyle(fontFamily = fontFamilyMeasureFont, fontSize = 10.sp),
+                maxLines = 1,
+                overflow = TextOverflow.StartEllipsis,
+                width = 50f
+            )
+
+        assertThat(paragraph.lineCount).isEqualTo(1)
+
+        assertThat(paragraph.isLineEllipsized(0)).isTrue()
+        assertThat(paragraph.getLineStart(0)).isEqualTo(0)
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            assertThat(paragraph.getLineEnd(0)).isEqualTo(text.length)
+        } else {
+            assertThat(paragraph.getLineEnd(0)).isEqualTo(5)
+        }
+    }
+
+    @Test
+    fun getLineMiddleEllipsisCount() {
+        val text = "aaaaabbbbbccccc"
+        val paragraph =
+            simpleParagraph(
+                text = text,
+                style = TextStyle(fontFamily = fontFamilyMeasureFont, fontSize = 10.sp),
+                maxLines = 1,
+                overflow = TextOverflow.MiddleEllipsis,
+                width = 50f
+            )
+
+        assertThat(paragraph.lineCount).isEqualTo(1)
+
+        assertThat(paragraph.isLineEllipsized(0)).isTrue()
+        assertThat(paragraph.getLineStart(0)).isEqualTo(0)
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
+            assertThat(paragraph.getLineEnd(0)).isEqualTo(text.length)
+        } else {
+            assertThat(paragraph.getLineEnd(0)).isEqualTo(5)
+        }
+    }
+
+    @Test
     fun lineHeight_inSp() {
         val text = "abcdefgh"
         val fontSize = 20f
diff --git a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/android/TextLayoutLineVisibleEndTest.kt b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/android/TextLayoutLineVisibleEndTest.kt
index 273ff95..0814b1a 100644
--- a/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/android/TextLayoutLineVisibleEndTest.kt
+++ b/compose/ui/ui-text/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/android/TextLayoutLineVisibleEndTest.kt
@@ -48,6 +48,47 @@
     }
 
     @Test
+    fun singleLine_withEllipsisStart() {
+        val text = "abcdefghij"
+        val textSize = 20.0f
+
+        val layout =
+            simpleLayout(
+                text = text,
+                textSize = textSize,
+                layoutWidth = textSize * 4,
+                maxLines = 1,
+                ellipsize = TextUtils.TruncateAt.START
+            )
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            assertThat(layout.getLineVisibleEnd(0)).isEqualTo(10)
+        } else {
+            assertThat(layout.getLineVisibleEnd(0)).isEqualTo(4)
+        }
+    }
+
+    @Test
+    fun singleLine_withEllipsisMiddle() {
+        val text = "abcdefghij"
+        val textSize = 20.0f
+
+        val layout =
+            simpleLayout(
+                text = text,
+                textSize = textSize,
+                layoutWidth = textSize * 4,
+                maxLines = 1,
+                ellipsize = TextUtils.TruncateAt.MIDDLE
+            )
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
+            assertThat(layout.getLineVisibleEnd(0)).isEqualTo(10)
+        } else {
+            assertThat(layout.getLineVisibleEnd(0)).isEqualTo(4)
+        }
+    }
+
+    @Test
     fun excludesLineBreak_whenMaxLinesPresent_withoutEllipsis() {
         val text = "abc\ndef"
         val textSize = 20.0f
@@ -81,6 +122,40 @@
     }
 
     @Test
+    fun excludesLineBreak_whenMaxLinesPresent_withEllipsisStart() {
+        val text = "abc\ndef"
+        val textSize = 20.0f
+
+        val layout =
+            simpleLayout(
+                text = text,
+                textSize = textSize,
+                layoutWidth = textSize * 10,
+                maxLines = 1,
+                ellipsize = TextUtils.TruncateAt.START
+            )
+
+        assertThat(layout.getLineVisibleEnd(0)).isEqualTo(3)
+    }
+
+    @Test
+    fun excludesLineBreak_whenMaxLinesPresent_withEllipsisMiddle() {
+        val text = "abc\ndef"
+        val textSize = 20.0f
+
+        val layout =
+            simpleLayout(
+                text = text,
+                textSize = textSize,
+                layoutWidth = textSize * 10,
+                maxLines = 1,
+                ellipsize = TextUtils.TruncateAt.MIDDLE
+            )
+
+        assertThat(layout.getLineVisibleEnd(0)).isEqualTo(3)
+    }
+
+    @Test
     fun excludesWhitespace_singleLineContent_withEllipsis() {
         val text = "abc def ghi"
         val textSize = 20.0f
@@ -166,7 +241,7 @@
             )
 
         assertThat(layout.getLineVisibleEnd(0)).isEqualTo(0)
-        assertThat(layout.getLineVisibleEnd(1)).isEqualTo(2) // ellipsis character
+        assertThat(layout.getLineVisibleEnd(1)).isEqualTo(1)
     }
 
     private fun simpleLayout(
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/android/TextLayout.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/android/TextLayout.android.kt
index 87c2aed..81d7f19 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/android/TextLayout.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/android/TextLayout.android.kt
@@ -119,7 +119,7 @@
     width: Float,
     val textPaint: TextPaint,
     @TextLayoutAlignment alignment: Int = DEFAULT_ALIGNMENT,
-    ellipsize: TextUtils.TruncateAt? = null,
+    private val ellipsize: TextUtils.TruncateAt? = null,
     @TextDirection textDirectionHeuristic: Int = DEFAULT_TEXT_DIRECTION,
     lineSpacingMultiplier: Float = DEFAULT_LINESPACING_MULTIPLIER,
     @Px lineSpacingExtra: Float = DEFAULT_LINESPACING_EXTRA,
@@ -457,13 +457,13 @@
      * returns the length of the text.
      */
     fun getLineEnd(lineIndex: Int): Int =
-        if (layout.getEllipsisStart(lineIndex) == 0) { // no ellipsis
-            layout.getLineEnd(lineIndex)
-        } else {
+        if (layout.isLineEllipsized(lineIndex) && ellipsize == TextUtils.TruncateAt.END) {
             // Layout#getLineEnd usually gets the end of text for the last line even if ellipsis
             // happens. However, if LF character is included in the ellipsized region, getLineEnd
             // returns LF character offset. So, use end of text for line end here.
             layout.text.length
+        } else {
+            layout.getLineEnd(lineIndex)
         }
 
     /**
@@ -471,10 +471,10 @@
      * whitespaces are not counted as visible characters.
      */
     fun getLineVisibleEnd(lineIndex: Int): Int =
-        if (layout.getEllipsisStart(lineIndex) == 0) { // no ellipsis
-            layoutHelper.getLineVisibleEnd(lineIndex)
-        } else {
+        if (layout.isLineEllipsized(lineIndex) && ellipsize == TextUtils.TruncateAt.END) {
             layout.getLineStart(lineIndex) + layout.getEllipsisStart(lineIndex)
+        } else {
+            layoutHelper.getLineVisibleEnd(lineIndex)
         }
 
     fun isLineEllipsized(lineIndex: Int) = layout.isLineEllipsized(lineIndex)
diff --git a/compose/ui/ui-tooling-data/build.gradle b/compose/ui/ui-tooling-data/build.gradle
index ee30296..f3ac046 100644
--- a/compose/ui/ui-tooling-data/build.gradle
+++ b/compose/ui/ui-tooling-data/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import androidx.build.PlatformIdentifier
 
@@ -106,6 +108,7 @@
     description = "Compose tooling library data. This library provides data about compose" +
             " for different tooling purposes."
     legacyDisableKotlinStrictApiMode = true
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/ui/ui-tooling/build.gradle b/compose/ui/ui-tooling/build.gradle
index 560d52b..6c7d92f 100644
--- a/compose/ui/ui-tooling/build.gradle
+++ b/compose/ui/ui-tooling/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import androidx.build.PlatformIdentifier
 
@@ -117,6 +119,7 @@
     metalavaK2UastEnabled = false
     samples(project(":compose:animation:animation:animation-samples"))
     // samples(project(":compose:animation:animation-core:animation-core-samples")) TODO(b/318840087)
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/ui/ui-tooling/lint-baseline.xml b/compose/ui/ui-tooling/lint-baseline.xml
index 8d948c9..728c687 100644
--- a/compose/ui/ui-tooling/lint-baseline.xml
+++ b/compose/ui/ui-tooling/lint-baseline.xml
@@ -1,14 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.7.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.7.0-alpha02)" variant="all" version="8.7.0-alpha02">
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="        val current = stack.removeLastKt()"
-        errorLine2="                      ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/tooling/PreviewUtils.android.kt"/>
-    </issue>
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
 
     <issue
         id="BanThreadSleep"
diff --git a/compose/ui/ui-unit/build.gradle b/compose/ui/ui-unit/build.gradle
index a7627e1..91140c3 100644
--- a/compose/ui/ui-unit/build.gradle
+++ b/compose/ui/ui-unit/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import androidx.build.PlatformIdentifier
 
@@ -111,6 +113,7 @@
     legacyDisableKotlinStrictApiMode = true
     metalavaK2UastEnabled = false
     samples(project(":compose:ui:ui-unit:ui-unit-samples"))
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/ui/ui-unit/samples/build.gradle b/compose/ui/ui-unit/samples/build.gradle
index 42100f5..aed7f5f 100644
--- a/compose/ui/ui-unit/samples/build.gradle
+++ b/compose/ui/ui-unit/samples/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 
 plugins {
@@ -48,6 +50,7 @@
     type = LibraryType.SAMPLES
     inceptionYear = "2019"
     description = "Contains the sample code for the Androidx Compose UI Simple Unit Classes"
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/ui/ui-viewbinding/build.gradle b/compose/ui/ui-viewbinding/build.gradle
index 6390f95..45759c8 100644
--- a/compose/ui/ui-viewbinding/build.gradle
+++ b/compose/ui/ui-viewbinding/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
@@ -55,6 +57,7 @@
     description = "Compose integration with ViewBinding"
     legacyDisableKotlinStrictApiMode = true
     samples(project(":compose:ui:ui-viewbinding:ui-viewbinding-samples"))
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/ui/ui-viewbinding/samples/build.gradle b/compose/ui/ui-viewbinding/samples/build.gradle
index 05dbaa8..5d16a55 100644
--- a/compose/ui/ui-viewbinding/samples/build.gradle
+++ b/compose/ui/ui-viewbinding/samples/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 
 plugins {
@@ -55,6 +57,7 @@
     type = LibraryType.SAMPLES
     inceptionYear = "2019"
     description = "Contains the sample code for the Androidx Compose UI Simple Unit Classes"
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/ui/ui/build.gradle b/compose/ui/ui/build.gradle
index acc6d86..1a3ffab 100644
--- a/compose/ui/ui/build.gradle
+++ b/compose/ui/ui/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import androidx.build.KmpPlatformsKt
 import androidx.build.PlatformIdentifier
@@ -214,6 +216,7 @@
     legacyDisableKotlinStrictApiMode = true
     metalavaK2UastEnabled = false
     samples(project(":compose:ui:ui:ui-samples"))
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/ui/ui/samples/build.gradle b/compose/ui/ui/samples/build.gradle
index ed4179a..64e101b 100644
--- a/compose/ui/ui/samples/build.gradle
+++ b/compose/ui/ui/samples/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 
 plugins {
@@ -50,6 +52,7 @@
     type = LibraryType.SAMPLES
     inceptionYear = "2019"
     description = "Contains the sample code for the Androidx Compose UI Core Classes"
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 android {
diff --git a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/input/PlatformTextInputViewIntegrationTest.kt b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/input/PlatformTextInputViewIntegrationTest.kt
index 28fc17c..fd37d97 100644
--- a/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/input/PlatformTextInputViewIntegrationTest.kt
+++ b/compose/ui/ui/src/androidInstrumentedTest/kotlin/androidx/compose/ui/text/input/PlatformTextInputViewIntegrationTest.kt
@@ -18,17 +18,32 @@
 
 import android.view.View
 import android.view.inputmethod.BaseInputConnection
+import android.view.inputmethod.CursorAnchorInfo
 import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.ExtractedText
 import android.view.inputmethod.InputConnection
+import android.widget.EditText
+import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusEventModifierNode
+import androidx.compose.ui.focus.FocusState
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.input.pointer.MatrixPositionCalculator
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.platform.AndroidComposeView
 import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.platform.PlatformTextInputModifierNode
 import androidx.compose.ui.platform.establishTextInputSession
+import androidx.compose.ui.platform.platformTextInputServiceInterceptor
+import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.requestFocus
+import androidx.compose.ui.viewinterop.AndroidView
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
@@ -623,6 +638,79 @@
         }
     }
 
+    @Suppress("DEPRECATION")
+    @Test
+    fun focusSwitchToAndroidViewNonEditor_hidesKeyboard() {
+        lateinit var button: android.widget.Button
+        val inputMethodManager = TestInputMethodManager()
+        platformTextInputServiceInterceptor = { original ->
+            TextInputServiceAndroid(
+                view = (original as TextInputServiceAndroid).view,
+                rootPositionCalculator = FakeMatrixPositionCalculator,
+                inputMethodManager = inputMethodManager
+            )
+        }
+        rule.setContent {
+            Column {
+                Box(TestElement { node1 = it }.focusable().testTag("tag"))
+                AndroidView(
+                    factory = { context ->
+                        android.widget.Button(context).also {
+                            it.isFocusableInTouchMode = true
+                            button = it
+                        }
+                    }
+                )
+            }
+        }
+
+        rule.onNodeWithTag("tag").requestFocus()
+        rule.waitForIdle()
+
+        inputMethodManager.reset()
+
+        // fake TextField is active, a session is started. Now let's request focus on Button
+        rule.runOnUiThread { button.requestFocus() }
+
+        rule.runOnIdle {
+            assertThat(inputMethodManager.restartCalls).isEqualTo(1)
+            assertThat(inputMethodManager.hideKeyboardCalls).isEqualTo(1)
+        }
+    }
+
+    @Suppress("DEPRECATION")
+    @Test
+    fun focusSwitchToAndroidViewEditor_doesNotHideKeyboard() {
+        lateinit var editText: EditText
+        val inputMethodManager = TestInputMethodManager()
+        platformTextInputServiceInterceptor = { original ->
+            TextInputServiceAndroid(
+                view = (original as TextInputServiceAndroid).view,
+                rootPositionCalculator = FakeMatrixPositionCalculator,
+                inputMethodManager = inputMethodManager
+            )
+        }
+        rule.setContent {
+            Column {
+                Box(TestElement { node1 = it }.focusable().testTag("tag"))
+                AndroidView(factory = { EditText(it).also { editText = it } })
+            }
+        }
+
+        rule.onNodeWithTag("tag").requestFocus()
+
+        rule.waitForIdle()
+        inputMethodManager.reset()
+
+        // fake TextField is active, a session is started. Now let's request focus on EditText
+        rule.runOnUiThread { editText.requestFocus() }
+
+        rule.runOnIdle {
+            assertThat(inputMethodManager.restartCalls).isEqualTo(0)
+            assertThat(inputMethodManager.hideKeyboardCalls).isEqualTo(0)
+        }
+    }
+
     private fun setupContent() {
         rule.setContent {
             hostView = LocalView.current as AndroidComposeView
@@ -643,10 +731,89 @@
     }
 
     private class TestNode(var onNode: (PlatformTextInputModifierNode) -> Unit) :
-        Modifier.Node(), PlatformTextInputModifierNode {
+        Modifier.Node(), PlatformTextInputModifierNode, FocusEventModifierNode {
+
+        private var inputSessionJob: Job? = null
+
+        private fun startInputSession() {
+            inputSessionJob =
+                coroutineScope.launch {
+                    establishTextInputSession {
+                        launch {
+                            startInputMethod(
+                                object : TestInputMethodRequest(view) {
+                                    override fun createInputConnection(
+                                        outAttributes: EditorInfo
+                                    ): InputConnection = BaseInputConnection(view, true)
+                                }
+                            )
+                        }
+                        awaitCancellation()
+                    }
+                }
+        }
+
+        private fun disposeInputSession() {
+            inputSessionJob?.cancel()
+            inputSessionJob = null
+        }
 
         override fun onAttach() {
             onNode(this)
         }
+
+        override fun onFocusEvent(focusState: FocusState) {
+            if (focusState.isFocused) {
+                startInputSession()
+            } else {
+                disposeInputSession()
+            }
+        }
     }
 }
+
+private object FakeMatrixPositionCalculator : MatrixPositionCalculator {
+    override fun localToScreen(localTransform: Matrix) = Unit
+
+    override fun localToScreen(localPosition: Offset) = localPosition
+
+    override fun screenToLocal(positionOnScreen: Offset) = positionOnScreen
+}
+
+@Suppress("DEPRECATION")
+private class TestInputMethodManager : InputMethodManager {
+    var restartCalls = 0
+    var showKeyboardCalls = 0
+    var hideKeyboardCalls = 0
+
+    fun reset() {
+        restartCalls = 0
+        showKeyboardCalls = 0
+        hideKeyboardCalls = 0
+    }
+
+    override fun isActive(): Boolean = true
+
+    override fun restartInput() {
+        restartCalls++
+    }
+
+    override fun showSoftInput() {
+        showKeyboardCalls++
+    }
+
+    override fun hideSoftInput() {
+        hideKeyboardCalls++
+    }
+
+    override fun updateExtractedText(token: Int, extractedText: ExtractedText) {}
+
+    override fun updateSelection(
+        selectionStart: Int,
+        selectionEnd: Int,
+        compositionStart: Int,
+        compositionEnd: Int
+    ) {}
+
+    override fun updateCursorAnchorInfo(cursorAnchorInfo: CursorAnchorInfo) {}
+}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.android.kt
index edb02b4..14c0c52 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroid.android.kt
@@ -270,6 +270,19 @@
     }
 
     private fun processInputCommands() {
+        // If the associated view is not focused anymore, we should check whether the focus has
+        // transitioned into another Editor.
+        if (!view.isFocused) {
+            val focusedView = view.rootView.findFocus()
+            // If a view is focused and is an editor, we can skip the queued up commands since the
+            // new editor is going to manage the keyboard and the input session. Otherwise we should
+            // process the queue since it probably contains StopInput or HideKeyboard calls to
+            // clean up after us.
+            if (focusedView?.onCheckIsTextEditor() == true) {
+                textInputCommandQueue.clear()
+                return
+            }
+        }
         // Multiple commands may have been queued up in the channel while this function was
         // waiting to be resumed. We don't execute the commands as they come in because making a
         // bunch of calls to change the actual IME quickly can result in flickers. Instead, we
diff --git a/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroidCommandDebouncingTest.kt b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroidCommandDebouncingTest.kt
index f27a52f..4e7c151 100644
--- a/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroidCommandDebouncingTest.kt
+++ b/compose/ui/ui/src/androidUnitTest/kotlin/androidx/compose/ui/text/input/TextInputServiceAndroidCommandDebouncingTest.kt
@@ -40,6 +40,7 @@
 class TextInputServiceAndroidCommandDebouncingTest {
 
     private val view = mock<View>()
+    private val parentView = mock<View>()
     private val inputMethodManager = TestInputMethodManager()
     private val executor = Executor { runnable -> scope.launch { runnable.run() } }
     private val service =
@@ -54,8 +55,11 @@
 
     @Before
     fun setUp() {
-        // Default the view to focused because when it's not focused commands should be ignored.
+        // Default the view to focused. When it is not focused the commands should be processed
+        // according to whether the new focused View is an Editor.
         whenever(view.isFocused).thenReturn(true)
+        whenever(view.rootView).thenReturn(parentView)
+        whenever(parentView.findFocus()).thenReturn(null)
     }
 
     @After
@@ -249,15 +253,29 @@
     }
 
     @Test
-    fun commandsAreDrained_whenProcessedWithoutFocus() {
+    fun commandsAreNotDrained_whenProcessedWithoutFocus_and_focusDidNotSwitchToAnEditor() {
         whenever(view.isFocused).thenReturn(false)
+        val newFocusedView = mock<View>()
+        whenever(newFocusedView.onCheckIsTextEditor()).thenReturn(false)
+        whenever(parentView.findFocus()).thenReturn(newFocusedView)
         service.showSoftwareKeyboard()
-        service.hideSoftwareKeyboard()
-        scope.advanceUntilIdle()
-        whenever(view.isFocused).thenReturn(true)
         scope.advanceUntilIdle()
 
-        assertThat(inputMethodManager.showSoftInputCalls).isEqualTo(0)
+        assertThat(inputMethodManager.showSoftInputCalls).isEqualTo(1)
+    }
+
+    @Test
+    fun commandsAreDrained_whenProcessedWithoutFocus_and_focusSwitchedToAnEditor() {
+        whenever(view.isFocused).thenReturn(false)
+        val newFocusedView = mock<View>()
+        whenever(newFocusedView.onCheckIsTextEditor()).thenReturn(true)
+        whenever(parentView.findFocus()).thenReturn(newFocusedView)
+        service.stopInput()
+        service.hideSoftwareKeyboard()
+        scope.advanceUntilIdle()
+
+        assertThat(inputMethodManager.restartCalls).isEqualTo(0)
+        assertThat(inputMethodManager.hideSoftInputCalls).isEqualTo(0)
     }
 
     @Test
diff --git a/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/platform/Synchronization.commonStubs.kt b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/platform/Synchronization.commonStubs.kt
index b070325..677f81e 100644
--- a/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/platform/Synchronization.commonStubs.kt
+++ b/compose/ui/ui/src/commonStubsMain/kotlin/androidx/compose/ui/platform/Synchronization.commonStubs.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT")
+
 package androidx.compose.ui.platform
 
 internal actual inline fun <R> synchronized(lock: Any, block: () -> R): R = block()
diff --git a/constraintlayout/constraintlayout-compose-lint/src/main/java/androidx/constraintlayout/compose/lint/ConstraintLayoutComposeIssueRegistry.kt b/constraintlayout/constraintlayout-compose-lint/src/main/java/androidx/constraintlayout/compose/lint/ConstraintLayoutComposeIssueRegistry.kt
index 4802f95..77ec179 100644
--- a/constraintlayout/constraintlayout-compose-lint/src/main/java/androidx/constraintlayout/compose/lint/ConstraintLayoutComposeIssueRegistry.kt
+++ b/constraintlayout/constraintlayout-compose-lint/src/main/java/androidx/constraintlayout/compose/lint/ConstraintLayoutComposeIssueRegistry.kt
@@ -25,7 +25,7 @@
 private const val CL_COMPOSE_NEW_ISSUE = "new?component=323867&template=1023345"
 
 class ConstraintLayoutComposeIssueRegistry : IssueRegistry() {
-    override val api = 14
+    override val api = 16
 
     override val minApi = CURRENT_API
 
diff --git a/core/core-ktx/lint-baseline.xml b/core/core-ktx/lint-baseline.xml
index 0014bc0..3a5f0be 100644
--- a/core/core-ktx/lint-baseline.xml
+++ b/core/core-ktx/lint-baseline.xml
@@ -1,14 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.7.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.7.0-alpha02)" variant="all" version="8.7.0-alpha02">
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="                stack.removeLastKt()"
-        errorLine2="                ~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/core/view/ViewGroup.kt"/>
-    </issue>
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
 
     <issue
         id="ObsoleteSdkInt"
diff --git a/core/core-telecom/integration-tests/testapp/lint-baseline.xml b/core/core-telecom/integration-tests/testapp/lint-baseline.xml
new file mode 100644
index 0000000..c3ec914
--- /dev/null
+++ b/core/core-telecom/integration-tests/testapp/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
+
+    <issue
+        id="NewApi"
+        message="`&lt;androidx.core.telecom.test.CallingMainActivity>` requires API level 34 (current min is 21)"
+        errorLine1="        &lt;activity"
+        errorLine2="        ^">
+        <location
+            file="src/main/AndroidManifest.xml"/>
+    </issue>
+
+</issues>
diff --git a/core/core-telecom/integration-tests/testapp/src/main/res/layout/call_row.xml b/core/core-telecom/integration-tests/testapp/src/main/res/layout/call_row.xml
index b12fe73..0fed5a1 100644
--- a/core/core-telecom/integration-tests/testapp/src/main/res/layout/call_row.xml
+++ b/core/core-telecom/integration-tests/testapp/src/main/res/layout/call_row.xml
@@ -100,7 +100,7 @@
                 android:layout_height="wrap_content"
                 android:layout_weight="1"
                 android:backgroundTint="#F44336"
-                android:text="Disc." />
+                android:text="Disc" />
         </LinearLayout>
 
         <LinearLayout
diff --git a/core/core-telecom/lint-baseline.xml b/core/core-telecom/lint-baseline.xml
new file mode 100644
index 0000000..aee4819
--- /dev/null
+++ b/core/core-telecom/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
+
+    <issue
+        id="NewApi"
+        message="`&lt;androidx.core.telecom.internal.JetpackConnectionService>` requires API level 26 (current min is 21)"
+        errorLine1="        &lt;service"
+        errorLine2="        ^">
+        <location
+            file="src/main/AndroidManifest.xml"/>
+    </issue>
+
+</issues>
diff --git a/core/core/lint-baseline.xml b/core/core/lint-baseline.xml
index d59a3bf..865f9051 100644
--- a/core/core/lint-baseline.xml
+++ b/core/core/lint-baseline.xml
@@ -1,5 +1,14 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.7.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.7.0-alpha02)" variant="all" version="8.7.0-alpha02">
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
+
+    <issue
+        id="NewApi"
+        message="`&lt;androidx.core.app.CoreComponentFactory>` requires API level 28 (current min is 21)"
+        errorLine1="    &lt;application"
+        errorLine2="    ^">
+        <location
+            file="src/main/AndroidManifest.xml"/>
+    </issue>
 
     <issue
         id="NewApi"
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 5fb9530..18803dd 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -337,4 +337,7 @@
 Failed sysctl call: hw\.nperflevels, Error code: 2
 # > Task :benchmark:benchmark-macro:processReleaseAndroidTestManifest
 \$SUPPORT/benchmark/benchmark\-macro/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-receiver\#androidx\.profileinstaller\.ProfileInstallReceiver@android:enabled was tagged at AndroidManifest\.xml:[0-9]+ to replace other declarations but no other declaration present
\ No newline at end of file
+receiver\#androidx\.profileinstaller\.ProfileInstallReceiver@android:enabled was tagged at AndroidManifest\.xml:[0-9]+ to replace other declarations but no other declaration present
+# b/368050059
+WARNING\: .+: The current version of R8 implicitly keeps the default constructor for Proguard configuration rules that have no member pattern\. If the following rule should continue to keep the default constructor in the next major version of R8\, then it must be augmented with the member pattern \`\{ void \<init\>\(\)\; \}\` to explicitly keep the default constructor\:
+-keep.+
\ No newline at end of file
diff --git a/development/update_gradle.sh b/development/update_gradle.sh
new file mode 100755
index 0000000..b400ac4
--- /dev/null
+++ b/development/update_gradle.sh
@@ -0,0 +1,81 @@
+#!/bin/bash
+
+set -e
+
+# Check if the user has provided the version as an argument
+if [ -z "$1" ]; then
+  echo "Error: No version provided. Usage: $0 <gradle-version>"
+  exit 1
+fi
+
+VERSION="$1"
+DEST_DIR="../../tools/external/gradle"
+WRAPPER_FILES=("gradle/wrapper/gradle-wrapper.properties" "playground-common/gradle/wrapper/gradle-wrapper.properties")
+
+BASE_URL="https://services.gradle.org/distributions"
+ZIP_FILE="gradle-${VERSION}-bin.zip"
+SHA_FILE="${ZIP_FILE}.sha256"
+
+# Function to check if a URL is valid by checking the HTTP status code
+check_url() {
+  local url="$1"
+
+  echo "Checking URL: $url"
+
+  http_status=$(curl -L --silent --head --write-out "%{http_code}" --output /dev/null "$url")
+
+  if [ "$http_status" -ne 200 ]; then
+    echo "Error: URL returned status code $http_status. The file doesn't exist at: $url"
+    exit 1
+  else
+    echo "URL is valid: $url"
+  fi
+}
+
+check_url "$BASE_URL/$ZIP_FILE"
+check_url "$BASE_URL/$SHA_FILE"
+
+echo "Cleaning destination directory: $DEST_DIR"
+rm -rf "$DEST_DIR"/*
+mkdir -p "$DEST_DIR"
+
+echo "Downloading Gradle ${VERSION}..."
+curl -Lo "$DEST_DIR/$ZIP_FILE" "$BASE_URL/$ZIP_FILE"
+curl -Lo "$DEST_DIR/$SHA_FILE" "$BASE_URL/$SHA_FILE"
+
+GRADLE_SHA256SUM=$(cat "$DEST_DIR/$SHA_FILE")
+
+echo "Downloaded Gradle ${VERSION} with SHA256: $GRADLE_SHA256SUM"
+
+update_gradle_wrapper_properties() {
+  local file="$1"
+  echo "Updating $file..."
+
+  if [ "$(uname)" = "Darwin" ]; then
+    sed -i '' "
+      s|distributionUrl=.*tools/external/gradle/.*|distributionUrl=../../../../tools/external/gradle/${ZIP_FILE}|;
+      s|distributionUrl=https\\\://services.gradle.org/distributions/.*|distributionUrl=https\\\://services.gradle.org/distributions/${ZIP_FILE}|;
+      s|distributionSha256Sum=.*|distributionSha256Sum=${GRADLE_SHA256SUM}|
+    " "$file"
+  else
+    sed -i "
+      s|distributionUrl=.*tools/external/gradle/.*|distributionUrl=../../../../tools/external/gradle/${ZIP_FILE}|;
+      s|distributionUrl=https\\\://services.gradle.org/distributions/.*|distributionUrl=https\\\://services.gradle.org/distributions/${ZIP_FILE}|;
+      s|distributionSha256Sum=.*|distributionSha256Sum=${GRADLE_SHA256SUM}|
+    " "$file"
+  fi
+
+  echo "Updated $file."
+}
+
+for file in "${WRAPPER_FILES[@]}"; do
+  update_gradle_wrapper_properties "$file"
+done
+
+echo "Gradle binary downloaded, and the wrapper properties updated successfully!"
+
+echo "Testing the setup with './gradlew bOS --dry-run'..."
+if ./gradlew bOS --dry-run; then
+  echo "Download and setup successful!"
+  echo "You can now upload changes in $(pwd) and $DEST_DIR to Gerrit!"
+fi
diff --git a/development/update_studio.sh b/development/update_studio.sh
index 3fa7ebb..a0f3b09 100755
--- a/development/update_studio.sh
+++ b/development/update_studio.sh
@@ -7,8 +7,8 @@
 
 # Versions that the user should update when running this script
 echo Getting Studio version and link
-AGP_VERSION=${1:-8.7.0-alpha03}
-STUDIO_VERSION_STRING=${2:-"Android Studio Ladybug | 2024.1.3 Canary 3"}
+AGP_VERSION=${1:-8.8.0-alpha01}
+STUDIO_VERSION_STRING=${2:-"Android Studio Ladybug Feature Drop | 2024.2.2 Canary 1"}
 
 # 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"`
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index d6dd52b..0b4eb74 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -240,7 +240,7 @@
     kmpDocs(project(":lifecycle:lifecycle-viewmodel"))
     kmpDocs(project(":lifecycle:lifecycle-viewmodel-compose"))
     docs(project(":lifecycle:lifecycle-viewmodel-ktx"))
-    docs(project(":lifecycle:lifecycle-viewmodel-savedstate"))
+    kmpDocs(project(":lifecycle:lifecycle-viewmodel-savedstate"))
     kmpDocs(project(":lifecycle:lifecycle-viewmodel-testing"))
     docs(project(":loader:loader"))
     docs(project(":loader:loader-ktx"))
diff --git a/fragment/fragment-lint/src/main/java/androidx/fragment/lint/FragmentIssueRegistry.kt b/fragment/fragment-lint/src/main/java/androidx/fragment/lint/FragmentIssueRegistry.kt
index d610aff..0f9fa50 100644
--- a/fragment/fragment-lint/src/main/java/androidx/fragment/lint/FragmentIssueRegistry.kt
+++ b/fragment/fragment-lint/src/main/java/androidx/fragment/lint/FragmentIssueRegistry.kt
@@ -24,7 +24,7 @@
 @Suppress("UnstableApiUsage")
 class FragmentIssueRegistry : IssueRegistry() {
     // tests are run with this version. We ensure that with ApiLintVersionsTest
-    override val api = 14
+    override val api = 16
     override val minApi = CURRENT_API
     override val issues
         get() =
diff --git a/fragment/fragment-testing-lint/src/main/java/androidx/fragment/testing/lint/FragmentTestingIssueRegistry.kt b/fragment/fragment-testing-lint/src/main/java/androidx/fragment/testing/lint/FragmentTestingIssueRegistry.kt
index 186283d..883758a 100644
--- a/fragment/fragment-testing-lint/src/main/java/androidx/fragment/testing/lint/FragmentTestingIssueRegistry.kt
+++ b/fragment/fragment-testing-lint/src/main/java/androidx/fragment/testing/lint/FragmentTestingIssueRegistry.kt
@@ -22,7 +22,7 @@
 
 @Suppress("UnstableApiUsage")
 class FragmentTestingIssueRegistry : IssueRegistry() {
-    override val api = 14
+    override val api = 16
     override val minApi = CURRENT_API
     override val issues
         get() = listOf(GradleConfigurationDetector.ISSUE)
diff --git a/fragment/fragment-testing-manifest-lint/src/main/java/androidx/fragment/testing/manifest/lint/FragmentTestingManifestIssueRegistry.kt b/fragment/fragment-testing-manifest-lint/src/main/java/androidx/fragment/testing/manifest/lint/FragmentTestingManifestIssueRegistry.kt
index 80dc56e..fa1f235 100644
--- a/fragment/fragment-testing-manifest-lint/src/main/java/androidx/fragment/testing/manifest/lint/FragmentTestingManifestIssueRegistry.kt
+++ b/fragment/fragment-testing-manifest-lint/src/main/java/androidx/fragment/testing/manifest/lint/FragmentTestingManifestIssueRegistry.kt
@@ -22,7 +22,7 @@
 
 @Suppress("UnstableApiUsage")
 class FragmentTestingManifestIssueRegistry : IssueRegistry() {
-    override val api = 14
+    override val api = 16
     override val minApi = CURRENT_API
     override val issues
         get() = listOf(GradleConfigurationDetector.ISSUE)
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 1931b6b..3e2af46 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -2,16 +2,17 @@
 # -----------------------------------------------------------------------------
 # All of the following should be updated in sync.
 # -----------------------------------------------------------------------------
-androidGradlePlugin = "8.7.0-alpha02"
+androidGradlePlugin = "8.8.0-alpha01"
 # 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.7.0-alpha02"
+androidLint = "31.8.0-alpha01"
 # 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 = "2024.1.3.2"
+androidStudio = "2024.2.2.1"
 # -----------------------------------------------------------------------------
 
 androidLintMin = "31.1.0"
+androidLintPrev = "31.7.0-alpha02"
 androidxTestRunner = "1.6.1"
 androidxTestRules = "1.6.1"
 androidxTestMonitor = "1.7.1"
@@ -79,11 +80,14 @@
 androidLayoutlibApi = { module = "com.android.tools.layoutlib:layoutlib-api", version.ref = "androidLint" }
 androidLint = { module = "com.android.tools.lint:lint", version.ref = "androidLint" }
 androidLintMin = { module = "com.android.tools.lint:lint", version.ref = "androidLintMin" }
+androidLintPrev = { module = "com.android.tools.lint:lint", version.ref = "androidLintPrev" }
+androidLintPrevApi = { module = "com.android.tools.lint:lint-api", version.ref = "androidLintPrev" }
 androidLintApi = { module = "com.android.tools.lint:lint-api", version.ref = "androidLint" }
 androidLintMinApi = { module = "com.android.tools.lint:lint-api", version.ref = "androidLintMin" }
 androidLintChecks = { module = "com.android.tools.lint:lint-checks", version.ref = "androidLint" }
 androidLintChecksMin = { module = "com.android.tools.lint:lint-checks", version.ref = "androidLintMin" }
 androidLintTests = { module = "com.android.tools.lint:lint-tests", version.ref = "androidLint" }
+androidLintPrevTests = { module = "com.android.tools.lint:lint-tests", version.ref = "androidLintPrev" }
 androidToolsCommon = { module = "com.android.tools:common", version.ref = "androidLint" }
 androidToolsNinepatch = { module = "com.android.tools:ninepatch", version.ref = "androidLint" }
 androidToolsRepository= { module = "com.android.tools:repository", version.ref = "androidLint" }
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 9bd15e6..b5f8900 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=../../../../tools/external/gradle/gradle-8.10-bin.zip
-distributionSha256Sum=5b9c5eb3f9fc2c94abaea57d90bd78747ca117ddbbf96c859d3741181a12bf2a
+distributionUrl=../../../../tools/external/gradle/gradle-8.10.2-bin.zip
+distributionSha256Sum=31c55713e40233a8303827ceb42ca48a47267a0ad4bab9177123121e71524c26
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/graphics/graphics-core/lint-baseline.xml b/graphics/graphics-core/lint-baseline.xml
index 6cf93884..0fe19d3 100644
--- a/graphics/graphics-core/lint-baseline.xml
+++ b/graphics/graphics-core/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.7.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.7.0-alpha02)" variant="all" version="8.7.0-alpha02">
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
 
     <issue
         id="NewApi"
@@ -145,13 +145,4 @@
             file="src/main/java/androidx/graphics/lowlatency/BufferTransformHintResolver.kt"/>
     </issue>
 
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="            val fdSignalPair = pendingFileDescriptors.removeLastKt()"
-        errorLine2="                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/hardware/FileDescriptorMonitor.kt"/>
-    </issue>
-
 </issues>
diff --git a/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/PolygonValidationTest.kt b/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/PolygonValidationTest.kt
new file mode 100644
index 0000000..6b6973d
--- /dev/null
+++ b/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/PolygonValidationTest.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.graphics.shapes
+
+import androidx.test.filters.SmallTest
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+@SmallTest
+class PolygonValidationTest {
+    private val pentagonPoints =
+        floatArrayOf(
+            0.2f,
+            0.0f,
+            0.8f,
+            0.0f,
+            1.0f,
+            0.6f,
+            0.5f,
+            1.0f,
+            0.0f,
+            0.6f,
+        )
+
+    private val reverseOrientedPentagonPoints =
+        floatArrayOf(
+            0.2f,
+            0.0f,
+            0.0f,
+            0.6f,
+            0.5f,
+            1.0f,
+            1.0f,
+            0.6f,
+            0.8f,
+            0.0f,
+        )
+
+    @Test fun doesNotFixValidSharpPolygon() = staysUnchanged(RoundedPolygon(5))
+
+    @Test
+    fun doesNotFixValidRoundPolygon() =
+        staysUnchanged(RoundedPolygon(5, rounding = CornerRounding(0.5f)))
+
+    @Test
+    fun fixesAntiClockwiseOrientedPolygon() {
+        val valid = RoundedPolygon(pentagonPoints)
+
+        val broken = RoundedPolygon(reverseOrientedPentagonPoints)
+
+        fixes(broken, valid)
+    }
+
+    @Test
+    fun fixesAntiClockwiseOrientedRoundedPolygon() {
+        val valid = RoundedPolygon(pentagonPoints, rounding = CornerRounding(0.5f))
+
+        val broken = RoundedPolygon(reverseOrientedPentagonPoints, rounding = CornerRounding(0.5f))
+
+        fixes(broken, valid)
+    }
+
+    private fun staysUnchanged(polygon: RoundedPolygon) {
+        val copy = RoundedPolygon(polygon)
+        val fixedPolygon = PolygonValidator.fix(polygon)
+
+        assertTrue(polygon === fixedPolygon)
+        assertEquals(copy, polygon)
+    }
+
+    private fun fixes(broken: RoundedPolygon, expected: RoundedPolygon) {
+        val fixed = PolygonValidator.fix(broken)
+
+        assertFalse(broken == fixed)
+        assertPolygonsEqualish(expected, fixed)
+    }
+}
diff --git a/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/TestUtils.kt b/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/TestUtils.kt
index 8c66d27..f39f85e 100644
--- a/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/TestUtils.kt
+++ b/graphics/graphics-shapes/src/androidInstrumentedTest/kotlin/androidx/graphics/shapes/TestUtils.kt
@@ -72,6 +72,26 @@
     }
 }
 
+internal fun assertFeaturesEqualish(expected: Feature, actual: Feature) {
+    assertCubicListsEqualish(expected.cubics, actual.cubics)
+    assertEquals(expected::class, actual::class)
+
+    if (expected is Feature.Corner && actual is Feature.Corner) {
+        pointsEqualish(expected.vertex, actual.vertex)
+        pointsEqualish(expected.roundedCenter, actual.roundedCenter)
+        assertEquals(expected.convex, actual.convex)
+    }
+}
+
+internal fun assertPolygonsEqualish(expected: RoundedPolygon, actual: RoundedPolygon) {
+    assertCubicListsEqualish(expected.cubics, actual.cubics)
+
+    assertEquals(expected.features.size, actual.features.size)
+    for (i in expected.features.indices) {
+        assertFeaturesEqualish(expected.features[i], actual.features[i])
+    }
+}
+
 internal fun assertPointGreaterish(expected: Point, actual: Point) {
     assertTrue(actual.x >= expected.x - Epsilon)
     assertTrue(actual.y >= expected.y - Epsilon)
diff --git a/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Features.kt b/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Features.kt
index 8a2135d..ac51e30 100644
--- a/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Features.kt
+++ b/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/Features.kt
@@ -24,6 +24,8 @@
 internal abstract class Feature(val cubics: List<Cubic>) {
     internal abstract fun transformed(f: PointTransformer): Feature
 
+    internal abstract fun reversed(): Feature
+
     /**
      * Edges have only a list of the cubic curves which make up the edge. Edges lie between corners
      * and have no vertex or concavity; the curves are simply straight lines (represented by Cubic
@@ -41,6 +43,16 @@
                 }
             )
 
+        override fun reversed(): Edge {
+            val reversedCubics = mutableListOf<Cubic>()
+
+            for (i in cubics.lastIndex downTo 0) {
+                reversedCubics.add(cubics[i].reverse())
+            }
+
+            return Edge(reversedCubics)
+        }
+
         override fun toString(): String = "Edge"
     }
 
@@ -72,6 +84,16 @@
             )
         }
 
+        override fun reversed(): Corner {
+            val reversedCubics = mutableListOf<Cubic>()
+
+            for (i in cubics.lastIndex downTo 0) {
+                reversedCubics.add(cubics[i].reverse())
+            }
+
+            return Corner(reversedCubics, vertex, roundedCenter, !convex)
+        }
+
         override fun toString(): String {
             return "Corner: vertex=$vertex, center=$roundedCenter, convex=$convex"
         }
diff --git a/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/PolygonValidation.kt b/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/PolygonValidation.kt
new file mode 100644
index 0000000..e716f34
--- /dev/null
+++ b/graphics/graphics-shapes/src/commonMain/kotlin/androidx/graphics/shapes/PolygonValidation.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.graphics.shapes
+
+// @TODO: Make class public as soon as all validations are implemented
+
+/**
+ * Utility class to fix invalid [RoundedPolygon]s that will otherwise break [Morph]s in one way or
+ * another, as [RoundedPolygon] assumes correct input. Correct input meaning:
+ * - Closed geometry
+ * - Clockwise orientation of points
+ * - No self-intersections
+ * - No holes
+ */
+internal class PolygonValidator() {
+
+    companion object {
+
+        // @TODO: Update docs when other validations are implemented
+        /**
+         * Validates whether this [RoundedPolygon]'s orientation is clockwise and fixes it if
+         * necessary.
+         *
+         * @param polygon The [RoundedPolygon] to validate
+         * @return A new [RoundedPolygon] with fixed orientation, or the same [RoundedPolygon] as
+         *   given when it was already valid
+         */
+        fun fix(polygon: RoundedPolygon): RoundedPolygon {
+            var result = polygon
+
+            debugLog(LOG_TAG) { "Validating polygon..." }
+
+            if (isCWOriented(polygon)) {
+                debugLog(LOG_TAG) { "Passed clockwise validation!" }
+            } else {
+                debugLog(LOG_TAG) { "Polygon is oriented anti-clockwise, fixing orientation..." }
+                result = fixCWOrientation(polygon)
+            }
+
+            return result
+        }
+
+        private fun isCWOriented(polygon: RoundedPolygon): Boolean {
+            var signedArea = 0.0f
+
+            for (i in polygon.cubics.indices) {
+                val cubic = polygon.cubics[i]
+                signedArea += (cubic.anchor1X - cubic.anchor0X) * (cubic.anchor1Y + cubic.anchor0Y)
+            }
+
+            return signedArea < 0
+        }
+
+        private fun fixCWOrientation(polygon: RoundedPolygon): RoundedPolygon {
+            // Persist first feature to stay a Corner
+            val reversedFeatures = mutableListOf(polygon.features.first().reversed())
+
+            for (i in polygon.features.lastIndex downTo 1) {
+                reversedFeatures.add(polygon.features[i].reversed())
+            }
+
+            return RoundedPolygon(reversedFeatures, polygon.centerX, polygon.centerY)
+        }
+    }
+}
+
+private const val LOG_TAG = "PolygonValidation"
diff --git a/health/connect/connect-client/api/current.txt b/health/connect/connect-client/api/current.txt
index 43c2df3..fb15f0f 100644
--- a/health/connect/connect-client/api/current.txt
+++ b/health/connect/connect-client/api/current.txt
@@ -54,6 +54,7 @@
   @SuppressCompatibility @androidx.health.connect.client.feature.ExperimentalFeatureAvailabilityApi public interface HealthConnectFeatures {
     method public int getFeatureStatus(int feature);
     field public static final androidx.health.connect.client.HealthConnectFeatures.Companion Companion;
+    field public static final int FEATURE_READ_HEALTH_DATA_HISTORY = 4; // 0x4
     field public static final int FEATURE_READ_HEALTH_DATA_IN_BACKGROUND = 1; // 0x1
     field public static final int FEATURE_SKIN_TEMPERATURE = 2; // 0x2
     field public static final int FEATURE_STATUS_AVAILABLE = 2; // 0x2
@@ -61,6 +62,7 @@
   }
 
   public static final class HealthConnectFeatures.Companion {
+    field public static final int FEATURE_READ_HEALTH_DATA_HISTORY = 4; // 0x4
     field public static final int FEATURE_READ_HEALTH_DATA_IN_BACKGROUND = 1; // 0x1
     field public static final int FEATURE_SKIN_TEMPERATURE = 2; // 0x2
     field public static final int FEATURE_STATUS_AVAILABLE = 2; // 0x2
@@ -163,6 +165,7 @@
     method public static String getReadPermission(kotlin.reflect.KClass<? extends androidx.health.connect.client.records.Record> recordType);
     method public static String getWritePermission(kotlin.reflect.KClass<? extends androidx.health.connect.client.records.Record> recordType);
     field public static final androidx.health.connect.client.permission.HealthPermission.Companion Companion;
+    field public static final String PERMISSION_READ_HEALTH_DATA_HISTORY = "android.permission.health.READ_HEALTH_DATA_HISTORY";
     field public static final String PERMISSION_READ_HEALTH_DATA_IN_BACKGROUND = "android.permission.health.READ_HEALTH_DATA_IN_BACKGROUND";
     field public static final String PERMISSION_WRITE_EXERCISE_ROUTE = "android.permission.health.WRITE_EXERCISE_ROUTE";
   }
diff --git a/health/connect/connect-client/api/restricted_current.txt b/health/connect/connect-client/api/restricted_current.txt
index 4eefc3f..62b921b 100644
--- a/health/connect/connect-client/api/restricted_current.txt
+++ b/health/connect/connect-client/api/restricted_current.txt
@@ -54,6 +54,7 @@
   @SuppressCompatibility @androidx.health.connect.client.feature.ExperimentalFeatureAvailabilityApi public interface HealthConnectFeatures {
     method public int getFeatureStatus(int feature);
     field public static final androidx.health.connect.client.HealthConnectFeatures.Companion Companion;
+    field public static final int FEATURE_READ_HEALTH_DATA_HISTORY = 4; // 0x4
     field public static final int FEATURE_READ_HEALTH_DATA_IN_BACKGROUND = 1; // 0x1
     field public static final int FEATURE_SKIN_TEMPERATURE = 2; // 0x2
     field public static final int FEATURE_STATUS_AVAILABLE = 2; // 0x2
@@ -61,6 +62,7 @@
   }
 
   public static final class HealthConnectFeatures.Companion {
+    field public static final int FEATURE_READ_HEALTH_DATA_HISTORY = 4; // 0x4
     field public static final int FEATURE_READ_HEALTH_DATA_IN_BACKGROUND = 1; // 0x1
     field public static final int FEATURE_SKIN_TEMPERATURE = 2; // 0x2
     field public static final int FEATURE_STATUS_AVAILABLE = 2; // 0x2
@@ -163,6 +165,7 @@
     method public static String getReadPermission(kotlin.reflect.KClass<? extends androidx.health.connect.client.records.Record> recordType);
     method public static String getWritePermission(kotlin.reflect.KClass<? extends androidx.health.connect.client.records.Record> recordType);
     field public static final androidx.health.connect.client.permission.HealthPermission.Companion Companion;
+    field public static final String PERMISSION_READ_HEALTH_DATA_HISTORY = "android.permission.health.READ_HEALTH_DATA_HISTORY";
     field public static final String PERMISSION_READ_HEALTH_DATA_IN_BACKGROUND = "android.permission.health.READ_HEALTH_DATA_IN_BACKGROUND";
     field public static final String PERMISSION_WRITE_EXERCISE_ROUTE = "android.permission.health.WRITE_EXERCISE_ROUTE";
   }
diff --git a/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/PermissionSamples.kt b/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/PermissionSamples.kt
index 68cb5f4..289ab1f 100644
--- a/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/PermissionSamples.kt
+++ b/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/PermissionSamples.kt
@@ -24,6 +24,7 @@
 import androidx.health.connect.client.PermissionController
 import androidx.health.connect.client.feature.ExperimentalFeatureAvailabilityApi
 import androidx.health.connect.client.permission.HealthPermission
+import androidx.health.connect.client.permission.HealthPermission.Companion.PERMISSION_READ_HEALTH_DATA_HISTORY
 import androidx.health.connect.client.permission.HealthPermission.Companion.PERMISSION_READ_HEALTH_DATA_IN_BACKGROUND
 import androidx.health.connect.client.records.StepsRecord
 
@@ -70,6 +71,29 @@
     }
 }
 
+@OptIn(ExperimentalFeatureAvailabilityApi::class)
+@Sampled
+fun RequestHistoryReadPermission(features: HealthConnectFeatures, activity: ActivityResultCaller) {
+    val requestPermission =
+        activity.registerForActivityResult(
+            PermissionController.createRequestPermissionResultContract()
+        ) { grantedPermissions: Set<String> ->
+            if (PERMISSION_READ_HEALTH_DATA_HISTORY in grantedPermissions) {
+                // It will be possible to read data older than 30 days from now on
+            } else {
+                // Permission denied, it won't be possible to read data older than 30 days
+            }
+        }
+
+    if (
+        features.getFeatureStatus(HealthConnectFeatures.FEATURE_READ_HEALTH_DATA_HISTORY) ==
+            HealthConnectFeatures.FEATURE_STATUS_AVAILABLE
+    ) {
+        // The feature is available, request history read permissions
+        requestPermission.launch(setOf(PERMISSION_READ_HEALTH_DATA_HISTORY))
+    }
+}
+
 @Sampled
 suspend fun GetPermissions(permissionController: PermissionController) {
     val grantedPermissions = permissionController.getGrantedPermissions()
diff --git a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImplTest.kt b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImplTest.kt
index c27cd66..478dc93 100644
--- a/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImplTest.kt
+++ b/health/connect/connect-client/src/androidTest/java/androidx/health/connect/client/impl/HealthConnectClientUpsideDownImplTest.kt
@@ -135,7 +135,7 @@
         val features =
             listOf(
                 HealthConnectFeatures.FEATURE_READ_HEALTH_DATA_IN_BACKGROUND,
-                HealthConnectFeatures.FEATURE_HEALTH_DATA_HISTORIC_READ,
+                HealthConnectFeatures.FEATURE_READ_HEALTH_DATA_HISTORY,
                 HealthConnectFeatures.FEATURE_SKIN_TEMPERATURE,
                 HealthConnectFeatures.FEATURE_PLANNED_EXERCISE
             )
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/HealthConnectFeatures.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/HealthConnectFeatures.kt
index e39c275..74d8164 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/HealthConnectFeatures.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/HealthConnectFeatures.kt
@@ -47,7 +47,7 @@
         @RestrictTo(RestrictTo.Scope.LIBRARY) const val FEATURE_PLANNED_EXERCISE = 3
 
         /** Feature constant for reading health data history. */
-        @RestrictTo(RestrictTo.Scope.LIBRARY) const val FEATURE_HEALTH_DATA_HISTORIC_READ = 4
+        const val FEATURE_READ_HEALTH_DATA_HISTORY = 4
 
         @Retention(AnnotationRetention.SOURCE)
         @IntDef(
@@ -56,7 +56,7 @@
                     FEATURE_READ_HEALTH_DATA_IN_BACKGROUND,
                     FEATURE_SKIN_TEMPERATURE,
                     FEATURE_PLANNED_EXERCISE,
-                    FEATURE_HEALTH_DATA_HISTORIC_READ
+                    FEATURE_READ_HEALTH_DATA_HISTORY
                 ]
         )
         @RestrictTo(RestrictTo.Scope.LIBRARY)
@@ -86,6 +86,8 @@
                 FEATURE_READ_HEALTH_DATA_IN_BACKGROUND to
                     HealthConnectVersionInfo(platformVersion = SDK_EXT_13_PLATFORM_VERSION),
                 FEATURE_SKIN_TEMPERATURE to
+                    HealthConnectVersionInfo(platformVersion = SDK_EXT_13_PLATFORM_VERSION),
+                FEATURE_READ_HEALTH_DATA_HISTORY to
                     HealthConnectVersionInfo(platformVersion = SDK_EXT_13_PLATFORM_VERSION)
             )
     }
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/HealthPermission.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/HealthPermission.kt
index 1394ec9..d88df5a 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/HealthPermission.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/HealthPermission.kt
@@ -16,6 +16,7 @@
 package androidx.health.connect.client.permission
 
 import androidx.annotation.RestrictTo
+import androidx.health.connect.client.HealthConnectClient
 import androidx.health.connect.client.HealthConnectFeatures
 import androidx.health.connect.client.records.ActiveCaloriesBurnedRecord
 import androidx.health.connect.client.records.BasalBodyTemperatureRecord
@@ -163,6 +164,24 @@
         const val PERMISSION_READ_HEALTH_DATA_IN_BACKGROUND =
             PERMISSION_PREFIX + "READ_HEALTH_DATA_IN_BACKGROUND"
 
+        /**
+         * A permission that allows to read the entire history of health data (of any type).
+         *
+         * An attempt to read data older than 30 days without this permission will result in an
+         * error. This applies for the following api methods: [HealthConnectClient.readRecord],
+         * [HealthConnectClient.readRecords], [HealthConnectClient.aggregate],
+         * [HealthConnectClient.aggregateGroupByPeriod],
+         * [HealthConnectClient.aggregateGroupByDuration] and [HealthConnectClient.getChanges].
+         *
+         * This feature is dependent on the version of HealthConnect installed on the device. To
+         * check if it's available call [HealthConnectFeatures.getFeatureStatus] and pass
+         * [HealthConnectFeatures.FEATURE_READ_HEALTH_DATA_HISTORY] as an argument.
+         *
+         * @sample androidx.health.connect.client.samples.RequestHistoryReadPermission
+         */
+        const val PERMISSION_READ_HEALTH_DATA_HISTORY =
+            PERMISSION_PREFIX + "READ_HEALTH_DATA_HISTORY"
+
         // Read permissions for ACTIVITY.
         internal const val READ_ACTIVE_CALORIES_BURNED =
             PERMISSION_PREFIX + "READ_ACTIVE_CALORIES_BURNED"
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/HealthConnectClientImplTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/HealthConnectClientImplTest.kt
index 45237b9..41ebfd7 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/HealthConnectClientImplTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/HealthConnectClientImplTest.kt
@@ -182,7 +182,7 @@
         val features =
             listOf(
                 HealthConnectFeatures.FEATURE_READ_HEALTH_DATA_IN_BACKGROUND,
-                HealthConnectFeatures.FEATURE_HEALTH_DATA_HISTORIC_READ,
+                HealthConnectFeatures.FEATURE_READ_HEALTH_DATA_HISTORY,
                 HealthConnectFeatures.FEATURE_SKIN_TEMPERATURE,
                 HealthConnectFeatures.FEATURE_PLANNED_EXERCISE
             )
diff --git a/ink/ink-authoring/api/current.txt b/ink/ink-authoring/api/current.txt
index 8bee50d..df33139 100644
--- a/ink/ink-authoring/api/current.txt
+++ b/ink/ink-authoring/api/current.txt
@@ -14,11 +14,14 @@
     ctor public InProgressStrokesView(android.content.Context context, optional android.util.AttributeSet? attrs);
     ctor public InProgressStrokesView(android.content.Context context, optional android.util.AttributeSet? attrs, optional @AttrRes int defStyleAttr);
     method public void addFinishedStrokesListener(androidx.ink.authoring.InProgressStrokesFinishedListener listener);
+    method public void addToStroke(android.view.MotionEvent event, int pointerId, androidx.ink.authoring.InProgressStrokeId strokeId);
+    method public void addToStroke(android.view.MotionEvent event, int pointerId, androidx.ink.authoring.InProgressStrokeId strokeId, optional android.view.MotionEvent? prediction);
     method public void addToStroke(androidx.ink.strokes.StrokeInputBatch inputs, androidx.ink.authoring.InProgressStrokeId strokeId);
     method public void addToStroke(androidx.ink.strokes.StrokeInputBatch inputs, androidx.ink.authoring.InProgressStrokeId strokeId, optional androidx.ink.strokes.StrokeInputBatch prediction);
     method public void cancelStroke(androidx.ink.authoring.InProgressStrokeId strokeId);
     method public void cancelStroke(androidx.ink.authoring.InProgressStrokeId strokeId, optional android.view.MotionEvent? event);
     method public void eagerInit();
+    method public void finishStroke(android.view.MotionEvent event, int pointerId, androidx.ink.authoring.InProgressStrokeId strokeId);
     method public void finishStroke(androidx.ink.strokes.StrokeInput input, androidx.ink.authoring.InProgressStrokeId strokeId);
     method public java.util.Map<androidx.ink.authoring.InProgressStrokeId,androidx.ink.strokes.Stroke> getFinishedStrokes();
     method public androidx.test.espresso.idling.CountingIdlingResource? getInProgressStrokeCounter();
@@ -31,6 +34,9 @@
     method public void setMaskPath(android.graphics.Path?);
     method public void setMotionEventToViewTransform(android.graphics.Matrix);
     method public void setRendererFactory(kotlin.jvm.functions.Function0<? extends androidx.ink.rendering.android.canvas.CanvasStrokeRenderer>);
+    method public androidx.ink.authoring.InProgressStrokeId startStroke(android.view.MotionEvent event, int pointerId, androidx.ink.brush.Brush brush);
+    method public androidx.ink.authoring.InProgressStrokeId startStroke(android.view.MotionEvent event, int pointerId, androidx.ink.brush.Brush brush, optional android.graphics.Matrix motionEventToWorldTransform);
+    method public androidx.ink.authoring.InProgressStrokeId startStroke(android.view.MotionEvent event, int pointerId, androidx.ink.brush.Brush brush, optional android.graphics.Matrix motionEventToWorldTransform, optional android.graphics.Matrix strokeToWorldTransform);
     method public androidx.ink.authoring.InProgressStrokeId startStroke(androidx.ink.strokes.StrokeInput input, androidx.ink.brush.Brush brush);
     property public final androidx.test.espresso.idling.CountingIdlingResource? inProgressStrokeCounter;
     property public final android.graphics.Path? maskPath;
diff --git a/ink/ink-authoring/api/restricted_current.txt b/ink/ink-authoring/api/restricted_current.txt
index 8bee50d..df33139 100644
--- a/ink/ink-authoring/api/restricted_current.txt
+++ b/ink/ink-authoring/api/restricted_current.txt
@@ -14,11 +14,14 @@
     ctor public InProgressStrokesView(android.content.Context context, optional android.util.AttributeSet? attrs);
     ctor public InProgressStrokesView(android.content.Context context, optional android.util.AttributeSet? attrs, optional @AttrRes int defStyleAttr);
     method public void addFinishedStrokesListener(androidx.ink.authoring.InProgressStrokesFinishedListener listener);
+    method public void addToStroke(android.view.MotionEvent event, int pointerId, androidx.ink.authoring.InProgressStrokeId strokeId);
+    method public void addToStroke(android.view.MotionEvent event, int pointerId, androidx.ink.authoring.InProgressStrokeId strokeId, optional android.view.MotionEvent? prediction);
     method public void addToStroke(androidx.ink.strokes.StrokeInputBatch inputs, androidx.ink.authoring.InProgressStrokeId strokeId);
     method public void addToStroke(androidx.ink.strokes.StrokeInputBatch inputs, androidx.ink.authoring.InProgressStrokeId strokeId, optional androidx.ink.strokes.StrokeInputBatch prediction);
     method public void cancelStroke(androidx.ink.authoring.InProgressStrokeId strokeId);
     method public void cancelStroke(androidx.ink.authoring.InProgressStrokeId strokeId, optional android.view.MotionEvent? event);
     method public void eagerInit();
+    method public void finishStroke(android.view.MotionEvent event, int pointerId, androidx.ink.authoring.InProgressStrokeId strokeId);
     method public void finishStroke(androidx.ink.strokes.StrokeInput input, androidx.ink.authoring.InProgressStrokeId strokeId);
     method public java.util.Map<androidx.ink.authoring.InProgressStrokeId,androidx.ink.strokes.Stroke> getFinishedStrokes();
     method public androidx.test.espresso.idling.CountingIdlingResource? getInProgressStrokeCounter();
@@ -31,6 +34,9 @@
     method public void setMaskPath(android.graphics.Path?);
     method public void setMotionEventToViewTransform(android.graphics.Matrix);
     method public void setRendererFactory(kotlin.jvm.functions.Function0<? extends androidx.ink.rendering.android.canvas.CanvasStrokeRenderer>);
+    method public androidx.ink.authoring.InProgressStrokeId startStroke(android.view.MotionEvent event, int pointerId, androidx.ink.brush.Brush brush);
+    method public androidx.ink.authoring.InProgressStrokeId startStroke(android.view.MotionEvent event, int pointerId, androidx.ink.brush.Brush brush, optional android.graphics.Matrix motionEventToWorldTransform);
+    method public androidx.ink.authoring.InProgressStrokeId startStroke(android.view.MotionEvent event, int pointerId, androidx.ink.brush.Brush brush, optional android.graphics.Matrix motionEventToWorldTransform, optional android.graphics.Matrix strokeToWorldTransform);
     method public androidx.ink.authoring.InProgressStrokeId startStroke(androidx.ink.strokes.StrokeInput input, androidx.ink.brush.Brush brush);
     property public final androidx.test.espresso.idling.CountingIdlingResource? inProgressStrokeCounter;
     property public final android.graphics.Path? maskPath;
diff --git a/ink/ink-authoring/build.gradle b/ink/ink-authoring/build.gradle
index ddd631b..4180c02 100644
--- a/ink/ink-authoring/build.gradle
+++ b/ink/ink-authoring/build.gradle
@@ -35,6 +35,7 @@
         implementation("androidx.fragment:fragment-ktx:1.3.0")
         implementation("androidx.test.espresso:espresso-idling-resource:3.5.0")
         implementation(project(":core:core"))
+        implementation(project(":core:core-ktx"))
         implementation(project(":ink:ink-nativeloader"))
         implementation(project(":ink:ink-geometry"))
         implementation(project(":ink:ink-brush"))
diff --git a/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/InProgressStrokesViewTest.kt b/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/InProgressStrokesViewTest.kt
index 7c13768..dece82e 100644
--- a/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/InProgressStrokesViewTest.kt
+++ b/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/InProgressStrokesViewTest.kt
@@ -86,12 +86,13 @@
     fun startStroke_showsStrokeWithNoCallback() {
         val stylusInputStream =
             InputStreamBuilder.stylusLine(startX = 25F, startY = 25F, endX = 105F, endY = 205F)
+        val downEvent = stylusInputStream.getDownEvent()
         activityScenarioRule.scenario.onActivity { activity ->
             @Suppress("UNUSED_VARIABLE")
             val unused =
                 activity.inProgressStrokesView.startStroke(
-                    stylusInputStream.getDownEvent(),
-                    pointerIndex = 0,
+                    downEvent,
+                    downEvent.getPointerId(0),
                     basicBrush(TestColors.AVOCADO_GREEN),
                 )
         }
@@ -105,15 +106,17 @@
         val stylusInputStream =
             InputStreamBuilder.stylusLine(startX = 25F, startY = 25F, endX = 105F, endY = 205F)
         activityScenarioRule.scenario.onActivity { activity ->
+            val downEvent = stylusInputStream.getDownEvent()
             val strokeId =
                 activity.inProgressStrokesView.startStroke(
-                    stylusInputStream.getDownEvent(),
-                    pointerIndex = 0,
+                    downEvent,
+                    downEvent.getPointerId(0),
                     basicBrush(TestColors.AVOCADO_GREEN),
                 )
+            val moveEvent = stylusInputStream.getNextMoveEvent()
             activity.inProgressStrokesView.addToStroke(
-                stylusInputStream.getNextMoveEvent(),
-                pointerIndex = 0,
+                moveEvent,
+                moveEvent.getPointerId(0),
                 strokeId,
                 prediction = null,
             )
@@ -128,17 +131,15 @@
         val stylusInputStream =
             InputStreamBuilder.stylusLine(startX = 25F, startY = 25F, endX = 105F, endY = 205F)
         activityScenarioRule.scenario.onActivity { activity ->
+            val downEvent = stylusInputStream.getDownEvent()
             val strokeId =
                 activity.inProgressStrokesView.startStroke(
-                    stylusInputStream.getDownEvent(),
-                    pointerIndex = 0,
+                    downEvent,
+                    downEvent.getPointerId(0),
                     basicBrush(TestColors.AVOCADO_GREEN),
                 )
-            activity.inProgressStrokesView.finishStroke(
-                stylusInputStream.getUpEvent(),
-                pointerIndex = 0,
-                strokeId,
-            )
+            val upEvent = stylusInputStream.getUpEvent()
+            activity.inProgressStrokesView.finishStroke(upEvent, upEvent.getPointerId(0), strokeId)
         }
 
         assertThatTakingScreenshotMatchesGolden("start_and_finish")
@@ -159,10 +160,11 @@
             InputStreamBuilder.stylusLine(startX = 25f, startY = 25f, endX = 105f, endY = 205f)
         activityScenarioRule.scenario.onActivity { activity ->
             val metrics = activity.resources.displayMetrics
+            val downEvent = stylusInputStream.getDownEvent()
             val strokeId =
                 activity.inProgressStrokesView.startStroke(
-                    stylusInputStream.getDownEvent(),
-                    pointerIndex = 0,
+                    downEvent,
+                    pointerId = downEvent.getPointerId(0),
                     basicBrush(TestColors.AVOCADO_GREEN),
                     // MotionEvent space uses pixels, so this transform sets world units equal to
                     // inches.
@@ -171,11 +173,8 @@
                     // Set one stroke unit equal to half a world unit (i.e. half an inch).
                     strokeToWorldTransform = Matrix().apply { setScale(0.5f, 0.5f) },
                 )
-            activity.inProgressStrokesView.finishStroke(
-                stylusInputStream.getUpEvent(),
-                pointerIndex = 0,
-                strokeId,
-            )
+            val upEvent = stylusInputStream.getUpEvent()
+            activity.inProgressStrokesView.finishStroke(upEvent, upEvent.getPointerId(0), strokeId)
         }
 
         yieldingSleep()
@@ -192,18 +191,19 @@
         val stylusInputStream =
             InputStreamBuilder.stylusLine(startX = 25f, startY = 25f, endX = 105f, endY = 205f)
         activityScenarioRule.scenario.onActivity { activity ->
+            val downEvent = stylusInputStream.getDownEvent()
             assertThrows(IllegalArgumentException::class.java) {
                 activity.inProgressStrokesView.startStroke(
-                    stylusInputStream.getDownEvent(),
-                    pointerIndex = 0,
+                    downEvent,
+                    downEvent.getPointerId(0),
                     basicBrush(TestColors.AVOCADO_GREEN),
                     motionEventToWorldTransform = Matrix().apply { setScale(0f, 0f) },
                 )
             }
             assertThrows(IllegalArgumentException::class.java) {
                 activity.inProgressStrokesView.startStroke(
-                    stylusInputStream.getDownEvent(),
-                    pointerIndex = 0,
+                    downEvent,
+                    downEvent.getPointerId(0),
                     basicBrush(TestColors.AVOCADO_GREEN),
                     strokeToWorldTransform = Matrix().apply { setScale(0f, 0f) },
                 )
@@ -223,10 +223,11 @@
             )
         lateinit var strokeId: InProgressStrokeId
         activityScenarioRule.scenario.onActivity { activity ->
+            val downEvent = stylusInputStream.getDownEvent()
             strokeId =
                 activity.inProgressStrokesView.startStroke(
-                    stylusInputStream.getDownEvent(),
-                    pointerIndex = 0,
+                    downEvent,
+                    downEvent.getPointerId(0),
                     basicBrush(TestColors.AVOCADO_GREEN),
                 )
         }
@@ -245,23 +246,22 @@
         val stylusInputStream =
             InputStreamBuilder.stylusLine(startX = 25F, startY = 25F, endX = 105F, endY = 205F)
         activityScenarioRule.scenario.onActivity { activity ->
+            val downEvent = stylusInputStream.getDownEvent()
             val strokeId =
                 activity.inProgressStrokesView.startStroke(
-                    stylusInputStream.getDownEvent(),
-                    pointerIndex = 0,
+                    downEvent,
+                    downEvent.getPointerId(0),
                     basicBrush(TestColors.AVOCADO_GREEN),
                 )
+            val moveEvent = stylusInputStream.getNextMoveEvent()
             activity.inProgressStrokesView.addToStroke(
-                stylusInputStream.getNextMoveEvent(),
-                pointerIndex = 0,
+                moveEvent,
+                moveEvent.getPointerId(0),
                 strokeId,
                 prediction = null,
             )
-            activity.inProgressStrokesView.finishStroke(
-                stylusInputStream.getUpEvent(),
-                pointerIndex = 0,
-                strokeId,
-            )
+            val upEvent = stylusInputStream.getUpEvent()
+            activity.inProgressStrokesView.finishStroke(upEvent, upEvent.getPointerId(0), strokeId)
         }
 
         assertThatTakingScreenshotMatchesGolden("start_and_add_and_finish")
@@ -330,23 +330,22 @@
                     postRotate(/* degrees= */ 15F)
                     postTranslate(100F, 200F)
                 }
+            val downEvent = stylusInputStream.getDownEvent()
             val strokeId =
                 activity.inProgressStrokesView.startStroke(
-                    stylusInputStream.getDownEvent(),
-                    pointerIndex = 0,
+                    downEvent,
+                    downEvent.getPointerId(0),
                     basicBrush(TestColors.AVOCADO_GREEN),
                 )
+            val moveEvent = stylusInputStream.getNextMoveEvent()
             activity.inProgressStrokesView.addToStroke(
-                stylusInputStream.getNextMoveEvent(),
-                pointerIndex = 0,
+                moveEvent,
+                moveEvent.getPointerId(0),
                 strokeId,
                 prediction = null,
             )
-            activity.inProgressStrokesView.finishStroke(
-                stylusInputStream.getUpEvent(),
-                pointerIndex = 0,
-                strokeId,
-            )
+            val upEvent = stylusInputStream.getUpEvent()
+            activity.inProgressStrokesView.finishStroke(upEvent, upEvent.getPointerId(0), strokeId)
         }
 
         assertThatTakingScreenshotMatchesGolden("motion_event_to_view_transform")
@@ -412,10 +411,11 @@
                 )
             lateinit var strokeId: InProgressStrokeId
             activityScenarioRule.scenario.onActivity { activity ->
+                val downEvent = stylusInputStream.getDownEvent()
                 strokeId =
                     activity.inProgressStrokesView.startStroke(
-                        stylusInputStream.getDownEvent(),
-                        pointerIndex = 0,
+                        downEvent,
+                        downEvent.getPointerId(0),
                         basicBrush(BRUSH_COLORS[strokeIndex]),
                     )
             }
@@ -423,9 +423,10 @@
             assertThat(finishedStrokeCohorts).hasSize(strokeIndex)
 
             activityScenarioRule.scenario.onActivity { activity ->
+                val moveEvent = stylusInputStream.getNextMoveEvent()
                 activity.inProgressStrokesView.addToStroke(
-                    stylusInputStream.getNextMoveEvent(),
-                    pointerIndex = 0,
+                    moveEvent,
+                    moveEvent.getPointerId(0),
                     strokeId,
                     prediction = null,
                 )
@@ -434,10 +435,11 @@
             assertThat(finishedStrokeCohorts).hasSize(strokeIndex)
 
             activityScenarioRule.scenario.onActivity { activity ->
+                val upEvent = stylusInputStream.getUpEvent()
                 activity.inProgressStrokesView.finishStroke(
-                    stylusInputStream.getUpEvent(),
-                    pointerIndex = 0,
-                    strokeId,
+                    upEvent,
+                    upEvent.getPointerId(0),
+                    strokeId
                 )
             }
             assertThatTakingScreenshotMatchesGolden(screenshotKey(strokeCount, "step3finish"))
@@ -474,10 +476,11 @@
         for (strokeIndex in strokeIds.indices) {
             val strokeCount = strokeIndex + 1
             activityScenarioRule.scenario.onActivity { activity ->
+                val downEvent = stylusInputStreams[strokeIndex].getDownEvent()
                 strokeIds[strokeIndex] =
                     activity.inProgressStrokesView.startStroke(
-                        stylusInputStreams[strokeIndex].getDownEvent(),
-                        pointerIndex = 0,
+                        downEvent,
+                        downEvent.getPointerId(0),
                         basicBrush(BRUSH_COLORS[strokeIndex]),
                     )
             }
@@ -488,9 +491,10 @@
         for (strokeIndex in strokeIds.indices) {
             val strokeCount = strokeIndex + 1
             activityScenarioRule.scenario.onActivity { activity ->
+                val moveEvent = stylusInputStreams[strokeIndex].getNextMoveEvent()
                 activity.inProgressStrokesView.addToStroke(
-                    stylusInputStreams[strokeIndex].getNextMoveEvent(),
-                    pointerIndex = 0,
+                    moveEvent,
+                    moveEvent.getPointerId(0),
                     checkNotNull(strokeIds[strokeIndex]),
                     prediction = null,
                 )
@@ -502,9 +506,10 @@
         for (strokeIndex in strokeIds.indices) {
             val strokeCount = strokeIndex + 1
             activityScenarioRule.scenario.onActivity { activity ->
+                val upEvent = stylusInputStreams[strokeIndex].getUpEvent()
                 activity.inProgressStrokesView.finishStroke(
-                    stylusInputStreams[strokeIndex].getUpEvent(),
-                    pointerIndex = 0,
+                    upEvent,
+                    upEvent.getPointerId(0),
                     checkNotNull(strokeIds[strokeIndex]),
                 )
             }
@@ -531,23 +536,26 @@
                         endX = 400F - 10F * strokeCount,
                         endY = 600F - 35F * strokeCount,
                     )
+                val downEvent = stylusInputStream.getDownEvent()
                 val strokeId =
                     activity.inProgressStrokesView.startStroke(
-                        stylusInputStream.getDownEvent(),
-                        pointerIndex = 0,
+                        downEvent,
+                        downEvent.getPointerId(0),
                         basicBrush(BRUSH_COLORS[strokeIndex]),
                     )
                 strokeIds.add(strokeId)
+                val moveEvent = stylusInputStream.getNextMoveEvent()
                 activity.inProgressStrokesView.addToStroke(
-                    stylusInputStream.getNextMoveEvent(),
-                    pointerIndex = 0,
+                    moveEvent,
+                    moveEvent.getPointerId(0),
                     strokeId,
                     prediction = null,
                 )
+                val upEvent = stylusInputStream.getUpEvent()
                 activity.inProgressStrokesView.finishStroke(
-                    stylusInputStream.getUpEvent(),
-                    pointerIndex = 0,
-                    strokeId,
+                    upEvent,
+                    upEvent.getPointerId(0),
+                    strokeId
                 )
             }
         }
@@ -583,7 +591,7 @@
                         pointerIdToStrokeId[pointerId] =
                             activity.inProgressStrokesView.startStroke(
                                 event,
-                                pointerIndex,
+                                pointerId,
                                 basicBrush(color = BRUSH_COLORS[pointerIdToStrokeId.size]),
                             )
                     }
@@ -593,7 +601,7 @@
                             val strokeId = checkNotNull(pointerIdToStrokeId[pointerId])
                             activity.inProgressStrokesView.addToStroke(
                                 event,
-                                pointerIndex,
+                                pointerId,
                                 strokeId,
                                 prediction = null,
                             )
@@ -607,11 +615,7 @@
                         if (event.actionMasked == actionToCancel) {
                             activity.inProgressStrokesView.cancelStroke(strokeId, event)
                         } else {
-                            activity.inProgressStrokesView.finishStroke(
-                                event,
-                                pointerIndex,
-                                strokeId
-                            )
+                            activity.inProgressStrokesView.finishStroke(event, pointerId, strokeId)
                         }
                     }
                 }
diff --git a/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/internal/AtMostOnceAfterSetUpTest.kt b/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/internal/AtMostOnceAfterSetUpTest.kt
index a06e702..4ce927c 100644
--- a/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/internal/AtMostOnceAfterSetUpTest.kt
+++ b/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/internal/AtMostOnceAfterSetUpTest.kt
@@ -16,14 +16,12 @@
 
 package androidx.ink.authoring.internal
 
-import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 
 @RunWith(JUnit4::class)
-@SmallTest
 class AtMostOnceAfterSetUpTest {
 
     @Test
diff --git a/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/internal/CanvasInProgressStrokesRenderHelperV33Test.kt b/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/internal/CanvasInProgressStrokesRenderHelperV33Test.kt
index f96fa79..a54a28d 100644
--- a/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/internal/CanvasInProgressStrokesRenderHelperV33Test.kt
+++ b/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/internal/CanvasInProgressStrokesRenderHelperV33Test.kt
@@ -61,8 +61,8 @@
 @RunWith(AndroidJUnit4::class)
 @MediumTest
 @SdkSuppress(
-    minSdkVersion = Build.VERSION_CODES.TIRAMISU,
     maxSdkVersion = Build.VERSION_CODES.TIRAMISU,
+    minSdkVersion = Build.VERSION_CODES.TIRAMISU
 )
 class CanvasInProgressStrokesRenderHelperV33Test {
 
@@ -233,7 +233,14 @@
         withActivity { activity ->
             val brush = Brush(family = StockBrushes.markerLatest, size = 10f, epsilon = 0.1f)
             val stroke = Stroke(brush, ImmutableStrokeInputBatch.EMPTY)
-            val handingOff = mapOf(InProgressStrokeId() to FinishedStroke(stroke, Matrix()))
+            val handingOff =
+                mapOf(
+                    InProgressStrokeId() to
+                        FinishedStroke(
+                            stroke,
+                            Matrix(),
+                        )
+                )
             activity.renderHelper.requestStrokeCohortHandoffToHwui(handingOff)
             verify(callback).setPauseStrokeCohortHandoffs(true)
             verify(callback).onStrokeCohortHandoffToHwui(handingOff)
diff --git a/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/internal/CanvasInProgressStrokesRenderHelperV33TestActivity.kt b/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/internal/CanvasInProgressStrokesRenderHelperV33TestActivity.kt
index 98014f4..dfdb7d6 100644
--- a/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/internal/CanvasInProgressStrokesRenderHelperV33TestActivity.kt
+++ b/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/internal/CanvasInProgressStrokesRenderHelperV33TestActivity.kt
@@ -91,29 +91,29 @@
             override fun draw(
                 canvas: Canvas,
                 stroke: Stroke,
-                strokeToCanvasTransform: AffineTransform
+                strokeToScreenTransform: AffineTransform
             ) {
-                renderer?.draw(canvas, stroke, strokeToCanvasTransform)
+                renderer?.draw(canvas, stroke, strokeToScreenTransform)
             }
 
-            override fun draw(canvas: Canvas, stroke: Stroke, strokeToCanvasTransform: Matrix) {
-                renderer?.draw(canvas, stroke, strokeToCanvasTransform)
+            override fun draw(canvas: Canvas, stroke: Stroke, strokeToScreenTransform: Matrix) {
+                renderer?.draw(canvas, stroke, strokeToScreenTransform)
             }
 
             override fun draw(
                 canvas: Canvas,
                 inProgressStroke: InProgressStroke,
-                strokeToCanvasTransform: AffineTransform,
+                strokeToScreenTransform: AffineTransform,
             ) {
-                renderer?.draw(canvas, inProgressStroke, strokeToCanvasTransform)
+                renderer?.draw(canvas, inProgressStroke, strokeToScreenTransform)
             }
 
             override fun draw(
                 canvas: Canvas,
                 inProgressStroke: InProgressStroke,
-                strokeToCanvasTransform: Matrix,
+                strokeToScreenTransform: Matrix,
             ) {
-                renderer?.draw(canvas, inProgressStroke, strokeToCanvasTransform)
+                renderer?.draw(canvas, inProgressStroke, strokeToScreenTransform)
             }
         }
 
diff --git a/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/internal/InProgressStrokePoolTest.kt b/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/internal/InProgressStrokePoolTest.kt
index ff7438f..5069211 100644
--- a/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/internal/InProgressStrokePoolTest.kt
+++ b/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/internal/InProgressStrokePoolTest.kt
@@ -17,7 +17,6 @@
 package androidx.ink.authoring.internal
 
 import androidx.ink.strokes.InProgressStroke
-import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.assertFailsWith
 import org.junit.Test
@@ -25,7 +24,6 @@
 import org.junit.runners.JUnit4
 
 @RunWith(JUnit4::class)
-@SmallTest
 class InProgressStrokePoolTest {
 
     @Test
diff --git a/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/internal/InProgressStrokesManagerTest.kt b/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/internal/InProgressStrokesManagerTest.kt
index 01558de..07894b6 100644
--- a/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/internal/InProgressStrokesManagerTest.kt
+++ b/ink/ink-authoring/src/androidInstrumentedTest/kotlin/androidx/ink/authoring/internal/InProgressStrokesManagerTest.kt
@@ -278,7 +278,14 @@
         val downEvent = MotionEvent.obtain(321, 321, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
 
         val inProgressStrokeId =
-            manager.startStroke(downEvent, 0, Matrix(), Matrix(), makeBrush(), 0f)
+            manager.startStroke(
+                downEvent,
+                downEvent.getPointerId(0),
+                Matrix(),
+                Matrix(),
+                makeBrush(),
+                0f
+            )
 
         assertThat(latencyDataRecorder.recordedData)
             .comparingElementsUsing(latencyDataEqual)
@@ -312,7 +319,14 @@
         // Set the pen down at t=321ms.
         val downEvent = MotionEvent.obtain(321, 321, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
         val inProgressStrokeId =
-            manager.startStroke(downEvent, 0, Matrix(), Matrix(), makeBrush(), 0f)
+            manager.startStroke(
+                downEvent,
+                downEvent.getPointerId(0),
+                Matrix(),
+                Matrix(),
+                makeBrush(),
+                0f
+            )
         // We already checked the reported LatencyData in a previous test. The clock ticked once.
         latencyDataRecorder.recordedData.clear()
         // Reset the fake clock, since we only care about latency reports from here onward. The
@@ -325,8 +339,8 @@
         val moveEvent1 = MotionEvent.obtain(321, 325, MotionEvent.ACTION_MOVE, 12f, 22f, 0)
         val moveEvent2 = MotionEvent.obtain(321, 329, MotionEvent.ACTION_MOVE, 12f, 22f, 0)
 
-        manager.addToStroke(moveEvent1, 0, inProgressStrokeId, null)
-        manager.addToStroke(moveEvent2, 0, inProgressStrokeId, null)
+        manager.addToStroke(moveEvent1, moveEvent1.getPointerId(0), inProgressStrokeId, null)
+        manager.addToStroke(moveEvent2, moveEvent2.getPointerId(0), inProgressStrokeId, null)
 
         assertThat(latencyDataRecorder.recordedData)
             .comparingElementsUsing(latencyDataEqual)
@@ -376,7 +390,14 @@
         // Set the pen down at t=321ms.
         val downEvent = MotionEvent.obtain(321, 321, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
         val inProgressStrokeId =
-            manager.startStroke(downEvent, 0, Matrix(), Matrix(), makeBrush(), 0f)
+            manager.startStroke(
+                downEvent,
+                downEvent.getPointerId(0),
+                Matrix(),
+                Matrix(),
+                makeBrush(),
+                0f
+            )
         // We already checked the reported LatencyData in a previous test. The clock ticked once.
         latencyDataRecorder.recordedData.clear()
         // Reset the fake clock, since we only care about latency reports from here onward. The
@@ -389,7 +410,7 @@
         val moveEvent = MotionEvent.obtain(321, 325, MotionEvent.ACTION_MOVE, 12f, 22f, 0)
         moveEvent.addBatch(329, 14f, 24f, 1f, 1f, 0)
 
-        manager.addToStroke(moveEvent, 0, inProgressStrokeId, null)
+        manager.addToStroke(moveEvent, moveEvent.getPointerId(0), inProgressStrokeId, null)
 
         assertThat(latencyDataRecorder.recordedData)
             .comparingElementsUsing(latencyDataEqual)
@@ -436,7 +457,14 @@
         // Set the pen down at t=321ms.
         val downEvent = MotionEvent.obtain(321, 321, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
         val inProgressStrokeId =
-            manager.startStroke(downEvent, 0, Matrix(), Matrix(), makeBrush(), 0f)
+            manager.startStroke(
+                downEvent,
+                downEvent.getPointerId(0),
+                Matrix(),
+                Matrix(),
+                makeBrush(),
+                0f
+            )
         // We already checked the reported LatencyData in a previous test.
         latencyDataRecorder.recordedData.clear()
         // Reset the fake clock, since we only care about latency reports from here onward. The
@@ -450,7 +478,12 @@
         val predictedMoveEvent = MotionEvent.obtain(321, 329, MotionEvent.ACTION_MOVE, 16f, 26f, 0)
         predictedMoveEvent.addBatch(333, 18f, 28f, 1f, 1f, 0)
 
-        manager.addToStroke(realMoveEvent, 0, inProgressStrokeId, predictedMoveEvent)
+        manager.addToStroke(
+            realMoveEvent,
+            realMoveEvent.getPointerId(0),
+            inProgressStrokeId,
+            predictedMoveEvent,
+        )
 
         // Now we should have latency data for all three input events.
         assertThat(latencyDataRecorder.recordedData)
@@ -509,7 +542,14 @@
         // Set the pen down at t=321ms.
         val downEvent = MotionEvent.obtain(321, 321, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
         val inProgressStrokeId =
-            manager.startStroke(downEvent, 0, Matrix(), Matrix(), makeBrush(), 0f)
+            manager.startStroke(
+                downEvent,
+                downEvent.getPointerId(0),
+                Matrix(),
+                Matrix(),
+                makeBrush(),
+                0f
+            )
         // We already checked the reported LatencyData in a previous test.
         latencyDataRecorder.recordedData.clear()
         // Reset the fake clock, since we only care about latency reports from here onward. The
@@ -526,7 +566,12 @@
         val predictedMoveEvent = MotionEvent.obtain(321, 333, MotionEvent.ACTION_MOVE, 16f, 26f, 0)
         predictedMoveEvent.addBatch(337, 18f, 28f, 1f, 1f, 0)
 
-        manager.addToStroke(realMoveEvent, 0, inProgressStrokeId, predictedMoveEvent)
+        manager.addToStroke(
+            realMoveEvent,
+            realMoveEvent.getPointerId(0),
+            inProgressStrokeId,
+            predictedMoveEvent,
+        )
 
         // Now we should have latency data for all three input events.
         assertThat(latencyDataRecorder.recordedData)
@@ -596,7 +641,14 @@
         // Set the pen down at t=321ms.
         val downEvent = MotionEvent.obtain(321, 321, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
         val inProgressStrokeId =
-            manager.startStroke(downEvent, 0, Matrix(), Matrix(), makeBrush(), 0f)
+            manager.startStroke(
+                downEvent,
+                downEvent.getPointerId(0),
+                Matrix(),
+                Matrix(),
+                makeBrush(),
+                0f
+            )
 
         // We already checked the reported LatencyData in a previous test.
         latencyDataRecorder.recordedData.clear()
@@ -617,8 +669,18 @@
         val realMoveEvent2 = MotionEvent.obtain(321, 337, MotionEvent.ACTION_MOVE, 18f, 28f, 0)
         val predictedMoveEvent2 = MotionEvent.obtain(321, 341, MotionEvent.ACTION_MOVE, 20f, 30f, 0)
 
-        manager.addToStroke(realMoveEvent1, 0, inProgressStrokeId, predictedMoveEvent1)
-        manager.addToStroke(realMoveEvent2, 0, inProgressStrokeId, predictedMoveEvent2)
+        manager.addToStroke(
+            realMoveEvent1,
+            realMoveEvent1.getPointerId(0),
+            inProgressStrokeId,
+            predictedMoveEvent1,
+        )
+        manager.addToStroke(
+            realMoveEvent2,
+            realMoveEvent2.getPointerId(0),
+            inProgressStrokeId,
+            predictedMoveEvent2,
+        )
 
         // Now we should have latency data for all the input events, even the old predictions that
         // were
@@ -716,13 +778,20 @@
         // Set the pen down at t=321ms.
         val downEvent = MotionEvent.obtain(321, 321, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
         val inProgressStrokeId =
-            manager.startStroke(downEvent, 0, Matrix(), Matrix(), makeBrush(), 0f)
+            manager.startStroke(
+                downEvent,
+                downEvent.getPointerId(0),
+                Matrix(),
+                Matrix(),
+                makeBrush(),
+                0f
+            )
 
         // Now move the pen at t=325ms and t=329ms.
         val moveEvent1 = MotionEvent.obtain(321, 325, MotionEvent.ACTION_MOVE, 12f, 22f, 0)
         val moveEvent2 = MotionEvent.obtain(321, 329, MotionEvent.ACTION_MOVE, 12f, 22f, 0)
-        manager.addToStroke(moveEvent1, 0, inProgressStrokeId, null)
-        manager.addToStroke(moveEvent2, 0, inProgressStrokeId, null)
+        manager.addToStroke(moveEvent1, moveEvent1.getPointerId(0), inProgressStrokeId, null)
+        manager.addToStroke(moveEvent2, moveEvent2.getPointerId(0), inProgressStrokeId, null)
 
         // We already checked the reported LatencyData in a previous test.
         latencyDataRecorder.recordedData.clear()
@@ -731,7 +800,7 @@
         // Finally, lift up the pen at t=333ms.
         val upEvent = MotionEvent.obtain(321, 333, MotionEvent.ACTION_UP, 12f, 22f, 0)
 
-        manager.finishStroke(upEvent, 0, inProgressStrokeId)
+        manager.finishStroke(upEvent, upEvent.getPointerId(0), inProgressStrokeId)
 
         assertThat(latencyDataRecorder.recordedData)
             .comparingElementsUsing(latencyDataEqual)
@@ -766,7 +835,15 @@
         setUpMockInProgressStrokesRenderHelperForSynchronousOperation(manager, clock)
         val downEvent = MotionEvent.obtain(321, 321, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
         @Suppress("UNUSED_VARIABLE")
-        val unused = manager.startStroke(downEvent, 0, Matrix(), Matrix(), makeBrush(), 0f)
+        val unused =
+            manager.startStroke(
+                downEvent,
+                downEvent.getPointerId(0),
+                Matrix(),
+                Matrix(),
+                makeBrush(),
+                0f
+            )
 
         val modifiedRegionCaptor = argumentCaptor<MutableBox>()
         verify(inProgressStrokesRenderHelper)
@@ -787,7 +864,15 @@
         setUpMockInProgressStrokesRenderHelperForSynchronousOperation(manager, clock)
         val downEvent = MotionEvent.obtain(321, 321, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
         @Suppress("UNUSED_VARIABLE")
-        val unused = manager.startStroke(downEvent, 0, Matrix(), Matrix(), makeBrush(), 0f)
+        val unused =
+            manager.startStroke(
+                downEvent,
+                downEvent.getPointerId(0),
+                Matrix(),
+                Matrix(),
+                makeBrush(),
+                0f
+            )
 
         val modifiedRegionCaptor = argumentCaptor<MutableBox>()
         verify(inProgressStrokesRenderHelper)
@@ -809,14 +894,21 @@
         setUpMockInProgressStrokesRenderHelperForSynchronousOperation(manager, clock)
         val downEvent = MotionEvent.obtain(321, 321, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
         val inProgressStrokeId =
-            manager.startStroke(downEvent, 0, Matrix(), Matrix(), makeBrush(), 0f)
+            manager.startStroke(
+                downEvent,
+                downEvent.getPointerId(0),
+                Matrix(),
+                Matrix(),
+                makeBrush(),
+                0f
+            )
         // The specifics of this are validated in a test focused on startStroke.
         verify(inProgressStrokesRenderHelper).prepareToDrawInModifiedRegion(any())
         verify(inProgressStrokesRenderHelper).drawInModifiedRegion(any<InProgressStroke>(), any())
         verify(inProgressStrokesRenderHelper).afterDrawInModifiedRegion()
 
         val moveEvent = MotionEvent.obtain(321, 325, MotionEvent.ACTION_MOVE, 10f, 20f, 0)
-        manager.addToStroke(moveEvent, 0, inProgressStrokeId, null)
+        manager.addToStroke(moveEvent, moveEvent.getPointerId(0), inProgressStrokeId, null)
 
         // Each being called a second time - the first time was from startStroke.
         val modifiedRegionCaptor = argumentCaptor<MutableBox>()
@@ -841,9 +933,16 @@
 
         val downEvent = MotionEvent.obtain(321, 321, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
         val inProgressStrokeId =
-            manager.startStroke(downEvent, 0, Matrix(), Matrix(), makeBrush(), 0f)
+            manager.startStroke(
+                downEvent,
+                downEvent.getPointerId(0),
+                Matrix(),
+                Matrix(),
+                makeBrush(),
+                0f
+            )
         val upEvent = MotionEvent.obtain(321, 333, MotionEvent.ACTION_UP, 12f, 22f, 0)
-        manager.finishStroke(upEvent, 0, inProgressStrokeId)
+        manager.finishStroke(upEvent, upEvent.getPointerId(0), inProgressStrokeId)
         manager.onStrokeCohortHandoffToHwuiComplete() // Unpause input processing to finish handoff.
 
         verify(inProgressStrokesRenderHelper).clear()
@@ -859,9 +958,16 @@
 
         val downEvent = MotionEvent.obtain(321, 321, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
         val inProgressStrokeId =
-            manager.startStroke(downEvent, 0, Matrix(), Matrix(), makeBrush(), 0f)
+            manager.startStroke(
+                downEvent,
+                downEvent.getPointerId(0),
+                Matrix(),
+                Matrix(),
+                makeBrush(),
+                0f
+            )
         val upEvent = MotionEvent.obtain(321, 333, MotionEvent.ACTION_UP, 12f, 22f, 0)
-        manager.finishStroke(upEvent, 0, inProgressStrokeId)
+        manager.finishStroke(upEvent, upEvent.getPointerId(0), inProgressStrokeId)
         manager.onStrokeCohortHandoffToHwuiComplete() // Unpause input processing to finish handoff.
 
         verify(inProgressStrokesRenderHelper, never()).clear()
@@ -882,7 +988,15 @@
 
         val downEvent = MotionEvent.obtain(321, 321, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
         @Suppress("UNUSED_VARIABLE")
-        val unused = manager.startStroke(downEvent, 0, Matrix(), Matrix(), makeBrush(), 0f)
+        val unused =
+            manager.startStroke(
+                downEvent,
+                downEvent.getPointerId(0),
+                Matrix(),
+                Matrix(),
+                makeBrush(),
+                0f
+            )
 
         assertThat(inProgressStrokePool.obtainCount).isEqualTo(1)
         assertThat(inProgressStrokePool.recycleCount).isEqualTo(0)
@@ -904,9 +1018,16 @@
 
         val downEvent = MotionEvent.obtain(321, 321, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
         val inProgressStrokeId =
-            manager.startStroke(downEvent, 0, Matrix(), Matrix(), makeBrush(), 0f)
+            manager.startStroke(
+                downEvent,
+                downEvent.getPointerId(0),
+                Matrix(),
+                Matrix(),
+                makeBrush(),
+                0f
+            )
         val upEvent = MotionEvent.obtain(321, 333, MotionEvent.ACTION_UP, 12f, 22f, 0)
-        manager.finishStroke(upEvent, 0, inProgressStrokeId)
+        manager.finishStroke(upEvent, upEvent.getPointerId(0), inProgressStrokeId)
         manager.onStrokeCohortHandoffToHwuiComplete() // Unpause input processing to finish handoff.
 
         assertThat(inProgressStrokePool.obtainCount).isEqualTo(1)
@@ -937,7 +1058,14 @@
             repeat(cohortSize) {
                 val downEvent = MotionEvent.obtain(321, 321, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
                 cohortStrokeIds.add(
-                    manager.startStroke(downEvent, 0, Matrix(), Matrix(), makeBrush(), 0f)
+                    manager.startStroke(
+                        downEvent,
+                        downEvent.getPointerId(0),
+                        Matrix(),
+                        Matrix(),
+                        makeBrush(),
+                        0f,
+                    )
                 )
                 assertThat(inProgressStrokePool.obtainCount).isEqualTo(it + 1) // it is 0-based
             }
@@ -950,7 +1078,7 @@
                 // together.
                 assertThat(inProgressStrokePool.recycleCount).isEqualTo(0)
                 val upEvent = MotionEvent.obtain(321, 333, MotionEvent.ACTION_UP, 12f, 22f, 0)
-                manager.finishStroke(upEvent, 0, strokeId)
+                manager.finishStroke(upEvent, upEvent.getPointerId(0), strokeId)
             }
             manager.onStrokeCohortHandoffToHwuiComplete() // Unpause input processing to finish
             // handoff.
@@ -982,7 +1110,15 @@
 
         val downTime = clock.getNextMillisTime()
         val downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
-        val strokeId = manager.startStroke(downEvent, 0, Matrix(), Matrix(), makeBrush(), 0f)
+        val strokeId =
+            manager.startStroke(
+                downEvent,
+                downEvent.getPointerId(0),
+                Matrix(),
+                Matrix(),
+                makeBrush(),
+                0f
+            )
         renderHelper.runRenderThreadToIdle()
         runUiThreadToEndOfFrame()
         val upEvent =
@@ -994,7 +1130,7 @@
                 22f,
                 0
             )
-        manager.finishStroke(upEvent, 0, strokeId)
+        manager.finishStroke(upEvent, upEvent.getPointerId(0), strokeId)
         renderHelper.runRenderThreadToIdle()
         runUiThreadToEndOfFrame()
 
@@ -1022,7 +1158,15 @@
 
         val downTime = clock.getNextMillisTime()
         val downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
-        val strokeId = manager.startStroke(downEvent, 0, Matrix(), Matrix(), makeBrush(), 0f)
+        val strokeId =
+            manager.startStroke(
+                downEvent,
+                downEvent.getPointerId(0),
+                Matrix(),
+                Matrix(),
+                makeBrush(),
+                0f
+            )
         renderHelper.runRenderThreadToIdle()
         runUiThreadToEndOfFrame()
         clock.advanceByMillis(1000)
@@ -1035,7 +1179,7 @@
                 40f,
                 0
             )
-        manager.addToStroke(moveEvent, 0, strokeId, null)
+        manager.addToStroke(moveEvent, moveEvent.getPointerId(0), strokeId, null)
         renderHelper.runRenderThreadToIdle()
         runUiThreadToEndOfFrame()
         clock.advanceByMillis(1000)
@@ -1048,7 +1192,7 @@
                 60f,
                 0
             )
-        manager.finishStroke(upEvent, 0, strokeId)
+        manager.finishStroke(upEvent, upEvent.getPointerId(0), strokeId)
         renderHelper.runRenderThreadToIdle()
         runUiThreadToEndOfFrame()
 
@@ -1191,7 +1335,7 @@
             assertThrows(IllegalArgumentException::class.java) {
                 manager.startStroke(
                     downEvent,
-                    pointerIndex = 0,
+                    pointerId = downEvent.getPointerId(0),
                     motionEventToWorldTransform = Matrix(),
                     strokeToWorldTransform = Matrix().apply { setScale(0f, 0f) },
                     brush = makeBrush(),
@@ -1202,6 +1346,138 @@
     }
 
     @Test
+    fun startStroke_withInvalidPointerId_throwsException() {
+        val clock = FakeClock()
+        val (manager, _, _) = makeAsyncManager(LatencyDataRecorder(), clock)
+        val downTime = clock.getNextMillisTime()
+        val downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
+
+        val error =
+            assertThrows(IllegalArgumentException::class.java) {
+                manager.startStroke(
+                    downEvent,
+                    pointerId = 10,
+                    motionEventToWorldTransform = Matrix(),
+                    strokeToWorldTransform = Matrix(),
+                    brush = makeBrush(),
+                    strokeUnitLengthCm = 1f,
+                )
+            }
+        assertThat(error).hasMessageThat().contains("Pointer id 10 is not present in event.")
+    }
+
+    @Test
+    fun addToStroke_withInvalidPointerId_throwsException() {
+        val clock = FakeClock()
+        val (manager, _, _) = makeAsyncManager(LatencyDataRecorder(), clock)
+        val downTime = clock.getNextMillisTime()
+        val downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
+
+        val strokeId =
+            manager.startStroke(
+                downEvent,
+                pointerId = downEvent.getPointerId(0),
+                motionEventToWorldTransform = Matrix(),
+                strokeToWorldTransform = Matrix(),
+                brush = makeBrush(),
+                strokeUnitLengthCm = 1f,
+            )
+        val moveEvent =
+            MotionEvent.obtain(downTime, downTime + 1000L, MotionEvent.ACTION_MOVE, 10f, 20f, 0)
+        val error =
+            assertThrows(IllegalArgumentException::class.java) {
+                manager.addToStroke(
+                    moveEvent,
+                    pointerId = 10,
+                    strokeId = strokeId,
+                    prediction = null
+                )
+            }
+        assertThat(error).hasMessageThat().contains("Pointer id 10 is not present in event.")
+    }
+
+    @Test
+    fun addToStroke_withMissingStrokeId_throwsException() {
+        val clock = FakeClock()
+        val (manager, _, _) = makeAsyncManager(LatencyDataRecorder(), clock)
+        val downTime = clock.getNextMillisTime()
+        val moveEvent =
+            MotionEvent.obtain(downTime, downTime + 1000L, MotionEvent.ACTION_MOVE, 10f, 20f, 0)
+        val error =
+            assertThrows(IllegalArgumentException::class.java) {
+                manager.addToStroke(
+                    moveEvent,
+                    pointerId = moveEvent.getPointerId(0),
+                    strokeId = InProgressStrokeId(),
+                    prediction = null,
+                )
+            }
+        assertThat(error).hasMessageThat().startsWith("Stroke with ID")
+    }
+
+    @Test
+    fun addToStroke_withMissingStrokeId_strokeInputBatchApi_throwsException() {
+        val clock = FakeClock()
+        val (manager, _, _) = makeAsyncManager(LatencyDataRecorder(), clock)
+        val error =
+            assertThrows(IllegalArgumentException::class.java) {
+                manager.addToStroke(
+                    ImmutableStrokeInputBatch.EMPTY,
+                    InProgressStrokeId(),
+                    ImmutableStrokeInputBatch.EMPTY,
+                )
+            }
+        assertThat(error).hasMessageThat().startsWith("Stroke with ID")
+    }
+
+    @Test
+    fun finishStroke_withMissingStrokeId_isIgnored() {
+        val clock = FakeClock()
+        val (manager, _, _) = makeAsyncManager(LatencyDataRecorder(), clock)
+        val downTime = clock.getNextMillisTime()
+        val moveEvent =
+            MotionEvent.obtain(downTime, downTime + 1000L, MotionEvent.ACTION_MOVE, 10f, 20f, 0)
+        // No error is thrown.
+        manager.finishStroke(
+            moveEvent,
+            pointerId = moveEvent.getPointerId(0),
+            strokeId = InProgressStrokeId(),
+        )
+    }
+
+    @Test
+    fun finishStroke_withMissingStrokeId_strokeInputBatchApi_isIgnored() {
+        val clock = FakeClock()
+        val (manager, _, _) = makeAsyncManager(LatencyDataRecorder(), clock)
+        manager.finishStroke(StrokeInput(), InProgressStrokeId())
+    }
+
+    @Test
+    fun finishStroke_withInvalidPointerId_throwsException() {
+        val clock = FakeClock()
+        val (manager, _, _) = makeAsyncManager(LatencyDataRecorder(), clock)
+        val downTime = clock.getNextMillisTime()
+        val downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
+
+        val strokeId =
+            manager.startStroke(
+                downEvent,
+                pointerId = downEvent.getPointerId(0),
+                motionEventToWorldTransform = Matrix(),
+                strokeToWorldTransform = Matrix(),
+                brush = makeBrush(),
+                strokeUnitLengthCm = 1f,
+            )
+        val upEvent =
+            MotionEvent.obtain(downTime, downTime + 1000L, MotionEvent.ACTION_UP, 10f, 20f, 0)
+        val error =
+            assertThrows(IllegalArgumentException::class.java) {
+                manager.finishStroke(upEvent, pointerId = 10, strokeId = strokeId)
+            }
+        assertThat(error).hasMessageThat().contains("Pointer id 10 is not present in event.")
+    }
+
+    @Test
     fun startStroke_shouldCombineTransformsCorrectly() {
         val clock = FakeClock()
         val (manager, renderHelper, runUiThreadToEndOfFrame) =
@@ -1226,7 +1502,7 @@
         val strokeId =
             manager.startStroke(
                 downEvent,
-                0,
+                downEvent.getPointerId(0),
                 motionEventToWorldTransform,
                 strokeToWorldTransform,
                 makeBrush(),
@@ -1243,7 +1519,7 @@
                 20f,
                 0
             )
-        manager.finishStroke(upEvent, 0, strokeId)
+        manager.finishStroke(upEvent, upEvent.getPointerId(0), strokeId)
         renderHelper.runRenderThreadToIdle()
         runUiThreadToEndOfFrame()
 
@@ -1286,7 +1562,15 @@
 
         val downTime = clock.getNextMillisTime()
         val downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
-        val strokeId = manager.startStroke(downEvent, 0, Matrix(), Matrix(), makeBrush(), 0f)
+        val strokeId =
+            manager.startStroke(
+                downEvent,
+                downEvent.getPointerId(0),
+                Matrix(),
+                Matrix(),
+                makeBrush(),
+                0f
+            )
         renderHelper.runRenderThreadToIdle()
         runUiThreadToEndOfFrame()
         val upEvent =
@@ -1298,7 +1582,7 @@
                 22f,
                 0
             )
-        manager.finishStroke(upEvent, 0, strokeId)
+        manager.finishStroke(upEvent, upEvent.getPointerId(0), strokeId)
         renderHelper.runRenderThreadToIdle()
         runUiThreadToEndOfFrame()
         assertThat(finishedStrokes).isEmpty()
@@ -1346,7 +1630,8 @@
         // Start a new stroke with the above brush.
         val downTime = clock.getNextMillisTime()
         val downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
-        val strokeId = manager.startStroke(downEvent, 0, Matrix(), Matrix(), brush, 0f)
+        val strokeId =
+            manager.startStroke(downEvent, downEvent.getPointerId(0), Matrix(), Matrix(), brush, 0f)
         renderHelper.runRenderThreadToIdle()
         runUiThreadToEndOfFrame()
 
@@ -1362,7 +1647,7 @@
                 22f,
                 0
             )
-        manager.finishStroke(upEvent, 0, strokeId)
+        manager.finishStroke(upEvent, upEvent.getPointerId(0), strokeId)
         renderHelper.runRenderThreadToIdle()
         runUiThreadToEndOfFrame()
         assertThat(finishedStrokes).isEmpty()
@@ -1401,9 +1686,16 @@
 
         val downEvent = MotionEvent.obtain(321, 321, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
         val inProgressStrokeId =
-            manager.startStroke(downEvent, 0, Matrix(), Matrix(), makeBrush(), 0f)
+            manager.startStroke(
+                downEvent,
+                downEvent.getPointerId(0),
+                Matrix(),
+                Matrix(),
+                makeBrush(),
+                0f
+            )
         val moveEvent = MotionEvent.obtain(321, 325, MotionEvent.ACTION_MOVE, 10f, 20f, 0)
-        manager.addToStroke(moveEvent, 0, inProgressStrokeId, null)
+        manager.addToStroke(moveEvent, moveEvent.getPointerId(0), inProgressStrokeId, null)
         val cancelEvent = MotionEvent.obtain(321, 333, MotionEvent.ACTION_CANCEL, 12f, 22f, 0)
         manager.cancelStroke(inProgressStrokeId, cancelEvent)
     }
@@ -1455,9 +1747,23 @@
 
         val downEvent = MotionEvent.obtain(321, 321, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
         val inProgressStrokeId1 =
-            manager.startStroke(downEvent, 0, Matrix(), Matrix(), makeBrush(), 0f)
+            manager.startStroke(
+                downEvent,
+                downEvent.getPointerId(0),
+                Matrix(),
+                Matrix(),
+                makeBrush(),
+                0f
+            )
         val inProgressStrokeId2 =
-            manager.startStroke(downEvent, 0, Matrix(), Matrix(), makeBrush(), 0f)
+            manager.startStroke(
+                downEvent,
+                downEvent.getPointerId(0),
+                Matrix(),
+                Matrix(),
+                makeBrush(),
+                0f
+            )
 
         assertThat(manager.flush(1000, TimeUnit.MILLISECONDS, cancelAllInProgress = false)).isTrue()
 
@@ -1487,9 +1793,25 @@
 
         val downEvent = MotionEvent.obtain(321, 321, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
         @Suppress("UNUSED_VARIABLE")
-        val unused1 = manager.startStroke(downEvent, 0, Matrix(), Matrix(), makeBrush(), 0f)
+        val unused1 =
+            manager.startStroke(
+                downEvent,
+                downEvent.getPointerId(0),
+                Matrix(),
+                Matrix(),
+                makeBrush(),
+                0f
+            )
         @Suppress("UNUSED_VARIABLE")
-        val unused2 = manager.startStroke(downEvent, 0, Matrix(), Matrix(), makeBrush(), 0f)
+        val unused2 =
+            manager.startStroke(
+                downEvent,
+                downEvent.getPointerId(0),
+                Matrix(),
+                Matrix(),
+                makeBrush(),
+                0f
+            )
 
         assertThat(manager.flush(1000, TimeUnit.MILLISECONDS, cancelAllInProgress = true)).isTrue()
 
@@ -1522,12 +1844,26 @@
 
         val downEvent = MotionEvent.obtain(321, 321, MotionEvent.ACTION_DOWN, 10f, 20f, 0)
         val inProgressStrokeId1 =
-            manager.startStroke(downEvent, 0, Matrix(), Matrix(), makeBrush(), 0f)
+            manager.startStroke(
+                downEvent,
+                downEvent.getPointerId(0),
+                Matrix(),
+                Matrix(),
+                makeBrush(),
+                0f
+            )
         val inProgressStrokeId2 =
-            manager.startStroke(downEvent, 0, Matrix(), Matrix(), makeBrush(), 0f)
+            manager.startStroke(
+                downEvent,
+                downEvent.getPointerId(0),
+                Matrix(),
+                Matrix(),
+                makeBrush(),
+                0f
+            )
         val upEvent = MotionEvent.obtain(321, 333, MotionEvent.ACTION_UP, 12f, 22f, 0)
-        manager.finishStroke(upEvent, 0, inProgressStrokeId1)
-        manager.finishStroke(upEvent, 0, inProgressStrokeId2)
+        manager.finishStroke(upEvent, upEvent.getPointerId(0), inProgressStrokeId1)
+        manager.finishStroke(upEvent, upEvent.getPointerId(0), inProgressStrokeId2)
 
         // These strokes aren't still in progress, they just haven't been handed off yet, so they
         // shouldn't be canceled.
diff --git a/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/InProgressStrokesView.kt b/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/InProgressStrokesView.kt
index 729021d..2abe7b07 100644
--- a/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/InProgressStrokesView.kt
+++ b/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/InProgressStrokesView.kt
@@ -31,6 +31,7 @@
 import androidx.annotation.RestrictTo
 import androidx.annotation.UiThread
 import androidx.annotation.VisibleForTesting
+import androidx.core.graphics.withMatrix
 import androidx.ink.authoring.internal.CanvasInProgressStrokesRenderHelperV21
 import androidx.ink.authoring.internal.CanvasInProgressStrokesRenderHelperV29
 import androidx.ink.authoring.internal.CanvasInProgressStrokesRenderHelperV33
@@ -314,7 +315,6 @@
             createRenderer = rendererFactory,
         )
 
-    /** Allows subclasses to provide their implementation of [InProgressStrokesRenderHelper]. */
     private fun inProgressStrokesRenderHelper(): InProgressStrokesRenderHelper {
         val existingInstance = renderHelper
         if (existingInstance != null) return existingInstance
@@ -399,11 +399,30 @@
     }
 
     /**
-     * Start building a stroke with the [event] data at [pointerIndex].
+     * Start building a stroke using a particular pointer within a [MotionEvent]. This would
+     * typically be followed by many calls to [addToStroke], and the sequence would end with a call
+     * to either [finishStroke] or [cancelStroke].
      *
-     * @param event The first [MotionEvent] as part of a Stroke's input data, typically an
-     *   ACTION_DOWN.
-     * @param pointerIndex The index of the relevant pointer in the [event].
+     * In most circumstances, prefer to use this function over [startStroke] that accepts a
+     * [StrokeInput].
+     *
+     * For optimum performance, it is strongly recommended to call [View.requestUnbufferedDispatch]
+     * using [event] and the [View] that generated [event] alongside calling this function. When
+     * requested this way, unbuffered dispatch mode will automatically end when the gesture is
+     * complete.
+     *
+     * @param event The first [MotionEvent] as part of a Stroke's input data, typically one with a
+     *   [MotionEvent.getActionMasked] value of [MotionEvent.ACTION_DOWN] or
+     *   [MotionEvent.ACTION_POINTER_DOWN], but not restricted to those action types.
+     * @param pointerId The identifier of the pointer within [event] to be used for inking, as
+     *   determined by [MotionEvent.getPointerId] and used as an input to
+     *   [MotionEvent.findPointerIndex]. Note that this is the ID of the pointer, not its index.
+     * @param brush Brush specification for the stroke being started. Note that the overall scaling
+     *   factor of [motionEventToWorldTransform] and [strokeToWorldTransform] combined should be
+     *   related to the value of [Brush.epsilon] - in general, the larger the combined
+     *   `motionEventToStrokeTransform` scaling factor is, the smaller on screen the stroke units
+     *   are, so [Brush.epsilon] should be a larger quantity of stroke units to maintain a similar
+     *   screen size.
      * @param motionEventToWorldTransform The matrix that transforms [event] coordinates into the
      *   client app's "world" coordinates, which typically is defined by how a client app's document
      *   is panned/zoomed/rotated. This defaults to the identity matrix, in which case the world
@@ -412,33 +431,29 @@
      *   density (e.g. scaled by 1 / [android.util.DisplayMetrics.density]) and any pan/zoom/rotate
      *   gestures that have been applied to the "camera" which portrays the "world" on the device
      *   screen. This matrix must be invertible.
-     * @param strokeToWorldTransform An optional matrix that transforms this stroke into the client
-     *   app's "world" coordinates, which allows the coordinates of the stroke to be defined in
-     *   something other than world coordinates. Defaults to the identity matrix, in which case the
-     *   stroke coordinate space is the same as world coordinate space. This matrix must be
-     *   invertible.
-     * @param brush Brush specification for the stroke being started. Note that if
-     *   [motionEventToWorldTransform] and [strokeToWorldTransform] combine to a [MotionEvent] to
-     *   stroke coordinates transform that scales stroke coordinate units to be very different in
-     *   size than screen pixels, then it is recommended to update the value of [Brush.epsilon] to
-     *   reflect that.
-     * @return The Stroke ID of the stroke being built, later used to identify which stroke is being
-     *   added to, finished, or canceled.
+     * @param strokeToWorldTransform Allows an object-specific (stroke-specific) coordinate space to
+     *   be defined in relation to the caller's "world" coordinate space. This defaults to the
+     *   identity matrix, which is typical for many use cases at the time of stroke construction. In
+     *   typical use cases, stroke coordinates and world coordinates may start to differ from one
+     *   another after stroke creation as a particular stroke is manipulated within the world, e.g.
+     *   it may be moved, scaled, or rotated relative to other content within an app's document.
+     *   This matrix must be invertible.
+     * @return The [InProgressStrokeId] of the stroke being built, later used to identify which
+     *   stroke is being updated with [addToStroke] or ended with [finishStroke] or [cancelStroke].
      * @throws IllegalArgumentException if [motionEventToWorldTransform] or [strokeToWorldTransform]
      *   is not invertible.
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
     @JvmOverloads
     public fun startStroke(
         event: MotionEvent,
-        pointerIndex: Int,
+        pointerId: Int,
         brush: Brush,
         motionEventToWorldTransform: Matrix = Matrix(),
         strokeToWorldTransform: Matrix = Matrix(),
     ): InProgressStrokeId =
         inProgressStrokesManager.startStroke(
             event,
-            pointerIndex,
+            pointerId,
             motionEventToWorldTransform,
             strokeToWorldTransform,
             brush,
@@ -475,52 +490,66 @@
     }
 
     /**
-     * Start building a stroke with the provided [input].
+     * Start building a stroke with the provided [input]. This would typically be followed by many
+     * calls to [addToStroke], and the sequence would end with a call to either [finishStroke] or
+     * [cancelStroke].
+     *
+     * In most circumstances, the [startStroke] overload that accepts a [MotionEvent] is more
+     * convenient. However, this overload using a [StrokeInput] is available for cases where the
+     * input data may not come directly from a [MotionEvent], such as receiving events over a
+     * network connection.
+     *
+     * If there is a way to request unbuffered dispatch from the source of the input data used here,
+     * equivalent to [View.requestUnbufferedDispatch] for unbuffered [MotionEvent] data, then be
+     * sure to request it for optimal performance.
      *
      * @param input The [StrokeInput] that started a stroke.
      * @param brush Brush specification for the stroke being started. Note that if stroke coordinate
-     *   units (the [StrokeInput.x] and [StrokeInput.y] fields of [input] are scaled to be very
+     *   units (the [StrokeInput.x] and [StrokeInput.y] fields of [input]) are scaled to be very
      *   different in size than screen pixels, then it is recommended to update the value of
      *   [Brush.epsilon] to reflect that.
-     * @return The Stroke ID of the stroke being built, later used to identify which stroke is being
-     *   added to, finished, or canceled.
+     * @return The [InProgressStrokeId] of the stroke being built, later used to identify which
+     *   stroke is being updated with [addToStroke] or ended with [finishStroke] or [cancelStroke].
      */
     public fun startStroke(input: StrokeInput, brush: Brush): InProgressStrokeId =
         inProgressStrokesManager.startStroke(input, brush)
 
     /**
-     * Add [event] data at [pointerIndex] to already started stroke with [strokeId].
+     * Add input data, from a particular pointer within a [MotionEvent], to an existing stroke.
      *
-     * @param event the next [MotionEvent] as part of a Stroke's input data, typically an
-     *   ACTION_MOVE.
-     * @param pointerIndex the index of the relevant pointer in the [event].
-     * @param strokeId the Stroke that is to be built upon with [event].
-     * @param prediction optional predicted [MotionEvent] containing predicted inputs between event
-     *   and the time of the next frame, as generated by
-     *   [androidx.input.motionprediction.MotionEventPredictor.predict].
+     * @param event The next [MotionEvent] as part of a stroke's input data, typically one with
+     *   [MotionEvent.getActionMasked] of [MotionEvent.ACTION_MOVE].
+     * @param pointerId The identifier of the pointer within [event] to be used for inking, as
+     *   determined by [MotionEvent.getPointerId] and used as an input to
+     *   [MotionEvent.findPointerIndex]. Note that this is the ID of the pointer, not its index.
+     * @param strokeId The [InProgressStrokeId] of the stroke to be built upon.
+     * @param prediction Predicted [MotionEvent] containing predicted inputs between [event] and the
+     *   time of the next frame. This value typically comes from
+     *   [androidx.input.motionprediction.MotionEventPredictor.predict]. It is technically optional,
+     *   but it is strongly recommended to achieve the best performance.
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
     @JvmOverloads
     public fun addToStroke(
         event: MotionEvent,
-        pointerIndex: Int,
+        pointerId: Int,
         strokeId: InProgressStrokeId,
         prediction: MotionEvent? = null,
     ): Unit =
         inProgressStrokesManager.addToStroke(
             event,
-            pointerIndex,
+            pointerId,
             strokeId,
             makeCorrectPrediction(prediction),
         )
 
     /**
-     * Add [inputs] to already started stroke with [strokeId].
+     * Add input data from a [StrokeInputBatch] to an existing stroke.
      *
-     * @param inputs the next [StrokeInputBatch] to be added to the stroke.
-     * @param strokeId the Stroke that is to be built upon with [inputs].
-     * @param prediction optional [StrokeInputBatch] containing predicted inputs after this portion
-     *   of the stroke.
+     * @param inputs The next [StrokeInputBatch] to be added to the stroke.
+     * @param strokeId The [InProgressStrokeId] of the stroke to be built upon.
+     * @param prediction Predicted [StrokeInputBatch] containing predicted inputs between [inputs]
+     *   and the time of the next frame. This can technically be empty, but it is strongly
+     *   recommended for it to be non-empty to achieve the best performance.
      */
     @JvmOverloads
     public fun addToStroke(
@@ -558,32 +587,59 @@
     }
 
     /**
-     * Complete the building of a stroke.
+     * Complete the building of a stroke, with the last input data coming from a particular pointer
+     * of a [MotionEvent].
      *
-     * @param event the last [MotionEvent] as part of a stroke, typically an ACTION_UP.
-     * @param pointerIndex the index of the relevant pointer.
-     * @param strokeId the stroke that is to be finished with the latest event.
+     * When the stroke no longer needs to be rendered by this [InProgressStrokesView] and can
+     * instead be rendered anywhere in the [View] hierarchy using [CanvasStrokeRenderer], the
+     * resulting [Stroke] object will be passed to the [InProgressStrokesFinishedListener] instances
+     * registered with this [InProgressStrokesView] using [addFinishedStrokesListener].
+     *
+     * @param event The last [MotionEvent] as part of a stroke's input data, typically one with
+     *   [MotionEvent.getActionMasked] of [MotionEvent.ACTION_UP] or
+     *   [MotionEvent.ACTION_POINTER_UP], but can also be other actions.
+     * @param pointerId The identifier of the pointer within [event] to be used for inking, as
+     *   determined by [MotionEvent.getPointerId] and used as an input to
+     *   [MotionEvent.findPointerIndex]. Note that this is the ID of the pointer, not its index.
+     * @param strokeId The [InProgressStrokeId] of the stroke to be finished.
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
     public fun finishStroke(
         event: MotionEvent,
-        pointerIndex: Int,
-        strokeId: InProgressStrokeId,
-    ): Unit = inProgressStrokesManager.finishStroke(event, pointerIndex, strokeId)
+        pointerId: Int,
+        strokeId: InProgressStrokeId
+    ): Unit = inProgressStrokesManager.finishStroke(event, pointerId, strokeId)
 
     /**
-     * Complete the building of a stroke.
+     * Complete the building of a stroke, with the last input data coming from a [StrokeInput].
      *
-     * @param input the last [StrokeInput] in the stroke.
-     * @param strokeId the stroke that is to be finished with the latest event.
+     * @param input The last [StrokeInput] in the stroke.
+     * @param strokeId The [InProgressStrokeId] of the stroke to be finished.
      */
     public fun finishStroke(input: StrokeInput, strokeId: InProgressStrokeId): Unit =
         inProgressStrokesManager.finishStroke(input, strokeId)
 
     /**
-     * Cancel the building of a stroke.
+     * Cancel the building of a stroke. It will no longer be visible within this
+     * [InProgressStrokesView], and no completed [Stroke] object will come through
+     * [InProgressStrokesFinishedListener].
      *
-     * @param strokeId the stroke to cancel.
+     * This is typically done for one of three reasons:
+     * 1. A [MotionEvent] with [MotionEvent.getActionMasked] of [MotionEvent.ACTION_CANCEL]. This
+     *    tends to be when an entire gesture has been canceled, for example when a parent [View]
+     *    uses [android.view.ViewGroup.onInterceptTouchEvent] to intercept and handle the gesture
+     *    itself.
+     * 2. A [MotionEvent] with [MotionEvent.getFlags] containing [MotionEvent.FLAG_CANCELED]. This
+     *    tends to be when the system has detected an unintentional touch, such as from the user
+     *    resting their palm on the screen while writing or drawing, after some events from that
+     *    unintentional pointer have already been delivered.
+     * 3. An app's business logic reinterprets a gesture previously used for inking as something
+     *    else, and the earlier inking may be seen as unintentional. For example, an app that uses
+     *    single-pointer gestures for inking and dual-pointer gestures for pan/zoom/rotate will
+     *    start inking when the first pointer goes down, but when the second pointer goes down it
+     *    may want to cancel the stroke from the first pointer rather than leave the small ink marks
+     *    on the screen.
+     *
+     * @param strokeId The [InProgressStrokeId] of the stroke to be canceled.
      * @param event The [MotionEvent] that led to this cancellation, if applicable.
      */
     @JvmOverloads
@@ -718,7 +774,9 @@
     override fun onDraw(canvas: Canvas) {
         @Suppress("UNUSED_VARIABLE")
         for ((strokeId, finishedStroke) in finishedStrokes) {
-            renderer.draw(canvas, finishedStroke.stroke, finishedStroke.strokeToViewTransform)
+            canvas.withMatrix(finishedStroke.strokeToViewTransform) {
+                renderer.draw(canvas, finishedStroke.stroke, finishedStroke.strokeToViewTransform)
+            }
         }
     }
 }
diff --git a/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/internal/CanvasInProgressStrokesRenderHelperV21.kt b/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/internal/CanvasInProgressStrokesRenderHelperV21.kt
index a2b1c7c..9f9e9bb 100644
--- a/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/internal/CanvasInProgressStrokesRenderHelperV21.kt
+++ b/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/internal/CanvasInProgressStrokesRenderHelperV21.kt
@@ -27,6 +27,7 @@
 import android.view.View
 import android.view.ViewGroup
 import androidx.annotation.UiThread
+import androidx.core.graphics.withMatrix
 import androidx.ink.authoring.ExperimentalLatencyDataApi
 import androidx.ink.authoring.InProgressStrokeId
 import androidx.ink.authoring.latency.LatencyData
@@ -146,7 +147,9 @@
         assertOnUiThread()
         val canvas =
             checkNotNull(canvasForCurrentDraw) { "Can only render during Callback.onDraw." }
-        renderer.draw(canvas, inProgressStroke, strokeToMainViewTransform)
+        canvas.withMatrix(strokeToMainViewTransform) {
+            renderer.draw(canvas, inProgressStroke, strokeToMainViewTransform)
+        }
     }
 
     override fun afterDrawInModifiedRegion() = Unit
diff --git a/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/internal/CanvasInProgressStrokesRenderHelperV29.kt b/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/internal/CanvasInProgressStrokesRenderHelperV29.kt
index 5756569..cf27833 100644
--- a/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/internal/CanvasInProgressStrokesRenderHelperV29.kt
+++ b/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/internal/CanvasInProgressStrokesRenderHelperV29.kt
@@ -40,6 +40,7 @@
 import androidx.annotation.RequiresApi
 import androidx.annotation.UiThread
 import androidx.annotation.WorkerThread
+import androidx.core.graphics.withMatrix
 import androidx.graphics.lowlatency.CanvasFrontBufferedRenderer
 import androidx.graphics.surface.SurfaceControlCompat
 import androidx.ink.authoring.ExperimentalLatencyDataApi
@@ -308,15 +309,15 @@
         assertOnRenderThread()
         check(onDrawState.duringDraw) { "Can only render during Callback.onDraw." }
 
-        renderer.draw(
+        val canvas =
             if (useOffScreenFrameBuffer) {
                 checkNotNull(onDrawState.offScreenCanvas)
             } else {
                 checkNotNull(onDrawState.frontBufferCanvas)
-            },
-            inProgressStroke,
-            strokeToMainViewTransform,
-        )
+            }
+        canvas.withMatrix(strokeToMainViewTransform) {
+            renderer.draw(canvas, inProgressStroke, strokeToMainViewTransform)
+        }
     }
 
     @WorkerThread
diff --git a/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/internal/CanvasInProgressStrokesRenderHelperV33.kt b/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/internal/CanvasInProgressStrokesRenderHelperV33.kt
index ad993fb..06ab596 100644
--- a/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/internal/CanvasInProgressStrokesRenderHelperV33.kt
+++ b/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/internal/CanvasInProgressStrokesRenderHelperV33.kt
@@ -42,6 +42,7 @@
 import androidx.annotation.UiThread
 import androidx.annotation.VisibleForTesting
 import androidx.annotation.WorkerThread
+import androidx.core.graphics.withMatrix
 import androidx.graphics.CanvasBufferedRenderer
 import androidx.graphics.surface.SurfaceControlCompat
 import androidx.hardware.SyncFenceCompat
@@ -650,11 +651,10 @@
             inProgressStroke: InProgressStroke,
             strokeToMainViewTransform: Matrix,
         ) {
-            renderer.draw(
-                checkNotNull(renderThreadState.offScreenCanvas),
-                inProgressStroke,
-                strokeToMainViewTransform,
-            )
+            val canvas = checkNotNull(renderThreadState.offScreenCanvas)
+            canvas.withMatrix(strokeToMainViewTransform) {
+                renderer.draw(canvas, inProgressStroke, strokeToMainViewTransform)
+            }
         }
 
         @WorkerThread
diff --git a/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/internal/InProgressStrokesManager.kt b/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/internal/InProgressStrokesManager.kt
index 77ad651..b06a42e 100644
--- a/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/internal/InProgressStrokesManager.kt
+++ b/ink/ink-authoring/src/androidMain/kotlin/androidx/ink/authoring/internal/InProgressStrokesManager.kt
@@ -320,11 +320,11 @@
     }
 
     /**
-     * Start building a stroke with the [event] data at [pointerIndex].
+     * Start building a stroke with the [event] data for [pointerId].
      *
      * @param event The first [MotionEvent] as part of a Stroke's input data, typically an
      *   ACTION_DOWN.
-     * @param pointerIndex The index of the relevant pointer in the [event].
+     * @param pointerId The index of the relevant pointer in the [event].
      * @param motionEventToWorldTransform The matrix that transforms [event] coordinates into the
      *   client app's "world" coordinates, which typically is defined by how a client app's document
      *   is panned/zoomed/rotated.
@@ -343,13 +343,15 @@
     @UiThread
     fun startStroke(
         event: MotionEvent,
-        pointerIndex: Int,
+        pointerId: Int,
         motionEventToWorldTransform: AndroidMatrix,
         strokeToWorldTransform: AndroidMatrix,
         brush: Brush,
         strokeUnitLengthCm: Float,
     ): InProgressStrokeId {
         val receivedActionTimeNanos = getNanoTime()
+        val pointerIndex = event.findPointerIndex(pointerId)
+        require(pointerIndex >= 0) { "Pointer id $pointerId is not present in event." }
         // Set up this stroke's matrix to be used to transform MotionEvent -> stroke coordinates.
         val motionEventToStrokeTransform =
             AndroidMatrix().also {
@@ -435,11 +437,11 @@
     }
 
     /**
-     * Add [event] data at [pointerIndex] to already started stroke with [strokeId].
+     * Add [event] data for [pointerId] to already started stroke with [strokeId].
      *
      * @param event the next [MotionEvent] as part of a Stroke's input data, typically an
      *   ACTION_MOVE.
-     * @param pointerIndex the index of the relevant pointer in the [event].
+     * @param pointerId the index of the relevant pointer in the [event].
      * @param strokeId the Stroke that is to be built upon with [event].
      * @param prediction optional predicted MotionEvent containing predicted inputs between event
      *   and the time of the next frame, as generated by MotionEventPredictor::predict.
@@ -447,13 +449,15 @@
     @UiThread
     fun addToStroke(
         event: MotionEvent,
-        pointerIndex: Int,
+        pointerId: Int,
         strokeId: InProgressStrokeId,
         prediction: MotionEvent?,
     ) {
         val receivedActionTimeNanos = getNanoTime()
         val strokeState = uiThreadState.startedStrokes[strokeId]
-        checkNotNull(strokeState) { "Stroke with ID $strokeId was not found." }
+        requireNotNull(strokeState) { "Stroke with ID $strokeId was not found." }
+        val pointerIndex = event.findPointerIndex(pointerId)
+        require(pointerIndex >= 0) { "Pointer id $pointerId is not present in event." }
         val addAction =
             (threadSharedState.addActionPool.poll() ?: AddAction()).apply {
                 check(realInputs.isEmpty())
@@ -521,7 +525,7 @@
         prediction: StrokeInputBatch,
     ) {
         val strokeState = uiThreadState.startedStrokes[strokeId]
-        checkNotNull(strokeState) { "Stroke with ID $strokeId was not found." }
+        requireNotNull(strokeState) { "Stroke with ID $strokeId was not found." }
         val addAction =
             (threadSharedState.addActionPool.poll() ?: AddAction()).apply {
                 check(realInputs.isEmpty())
@@ -549,13 +553,15 @@
      * Complete the building of a stroke.
      *
      * @param event the last [MotionEvent] as part of a stroke, typically an ACTION_UP.
-     * @param pointerIndex the index of the relevant pointer.
+     * @param pointerId the id of the relevant pointer.
      * @param strokeId the stroke that is to be finished with the latest event.
      */
     @UiThread
-    fun finishStroke(event: MotionEvent, pointerIndex: Int, strokeId: InProgressStrokeId) {
+    fun finishStroke(event: MotionEvent, pointerId: Int, strokeId: InProgressStrokeId) {
         val receivedActionTimeNanos = getNanoTime()
         val strokeState = uiThreadState.startedStrokes[strokeId] ?: return
+        val pointerIndex = event.findPointerIndex(pointerId)
+        require(pointerIndex >= 0) { "Pointer id $pointerId is not present in event." }
         finishStrokeInternal(
             // This ignores any historical inputs included in this MotionEvent. Typically, this
             // is called in response to an ACTION_UP, which doesn't have any. But potentially the
@@ -597,11 +603,10 @@
         endTimeMs: Long,
         latencyData: LatencyData? = null,
     ) {
-        val finishAction = FinishAction(input, strokeId, latencyData)
         uiThreadState.lastStrokeEndUptimeMs = endTimeMs
-        uiThreadState.startedStrokes.remove(strokeId)
+        uiThreadState.startedStrokes.remove(strokeId) ?: return
         uiThreadState.inputCompletedStrokes.add(strokeId)
-        queueInputToRenderThread(finishAction)
+        queueInputToRenderThread(FinishAction(input, strokeId, latencyData))
     }
 
     /**
diff --git a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/Brush.kt b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/Brush.kt
index 6ddee13..6b2c734 100644
--- a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/Brush.kt
+++ b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/Brush.kt
@@ -23,6 +23,7 @@
 import androidx.ink.brush.color.Color as ComposeColor
 import androidx.ink.brush.color.toArgb
 import androidx.ink.nativeloader.NativeLoader
+import androidx.ink.nativeloader.UsedByNative
 import kotlin.Float
 import kotlin.jvm.JvmStatic
 
@@ -317,7 +318,7 @@
     }
 
     /** Create underlying native object and return reference for all subsequent native calls. */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeCreateBrush(
         familyNativePointer: Long,
         colorRed: Float,
@@ -330,9 +331,7 @@
     ): Long
 
     /** Release the underlying memory allocated in [nativeCreateBrush]. */
-    private external fun nativeFreeBrush(
-        nativePointer: Long
-    ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun nativeFreeBrush(nativePointer: Long)
 
     public companion object {
         init {
diff --git a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushBehavior.kt b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushBehavior.kt
index 10f81dd..63492b5 100644
--- a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushBehavior.kt
+++ b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushBehavior.kt
@@ -18,6 +18,7 @@
 
 import androidx.annotation.RestrictTo
 import androidx.ink.nativeloader.NativeLoader
+import androidx.ink.nativeloader.UsedByNative
 import java.util.Collections.unmodifiableList
 import java.util.Collections.unmodifiableSet
 import kotlin.jvm.JvmField
@@ -250,25 +251,20 @@
     }
 
     /** Creates an underlying native brush behavior with no nodes and returns its memory address. */
-    private external fun nativeCreateEmptyBrushBehavior():
-        Long // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun nativeCreateEmptyBrushBehavior(): Long
 
     /**
      * Validates a native `BrushBehavior` and returns the pointer back, or deletes the native
      * `BrushBehavior` and throws an exception if it's not valid.
      */
-    private external fun nativeValidateOrDeleteAndThrow(
-        nativePointer: Long
-    ): Long // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun nativeValidateOrDeleteAndThrow(nativePointer: Long): Long
 
     /**
      * Release the underlying memory allocated in [nativeCreateBrushBehaviorLinear],
      * [nativeCreateBrushBehaviorPredefined], [nativeCreateBrushBehaviorSteps], or
      * [nativeCreateBrushBehaviorCubicBezier].
      */
-    private external fun nativeFreeBrushBehavior(
-        nativePointer: Long
-    ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun nativeFreeBrushBehavior(nativePointer: Long)
 
     public companion object {
         init {
@@ -295,7 +291,6 @@
     public class Source private constructor(@JvmField internal val value: Int) {
         internal fun toSimpleString(): String =
             when (this) {
-                CONSTANT_ZERO -> "CONSTANT_ZERO"
                 NORMALIZED_PRESSURE -> "NORMALIZED_PRESSURE"
                 TILT_IN_RADIANS -> "TILT_IN_RADIANS"
                 TILT_X_IN_RADIANS -> "TILT_X_IN_RADIANS"
@@ -366,91 +361,84 @@
 
         public companion object {
 
-            /**
-             * A source whose value is always zero. This can be used to provide a constant modifier
-             * to a target value. Normally this is not needed, because you can just set those
-             * modifiers directly on the [BrushTip], but it can become useful when combined with the
-             * [enabledToolTypes] and/or [isFallbackFor] fields to only conditionally enable it.
-             */
-            @JvmField public val CONSTANT_ZERO: Source = Source(0)
             /** Stylus or touch pressure with values reported in the range [0, 1]. */
-            @JvmField public val NORMALIZED_PRESSURE: Source = Source(1)
+            @JvmField public val NORMALIZED_PRESSURE: Source = Source(0)
             /** Stylus tilt with values reported in the range [0, π/2] radians. */
-            @JvmField public val TILT_IN_RADIANS: Source = Source(2)
+            @JvmField public val TILT_IN_RADIANS: Source = Source(1)
             /**
              * Stylus tilt along the x axis in the range [-π/2, π/2], with a positive value
              * corresponding to tilt toward the respective positive axis. In order for those values
              * to be reported, both tilt and orientation have to be populated on the StrokeInput.
              */
-            @JvmField public val TILT_X_IN_RADIANS: Source = Source(3)
+            @JvmField public val TILT_X_IN_RADIANS: Source = Source(2)
             /**
              * Stylus tilt along the y axis in the range [-π/2, π/2], with a positive value
              * corresponding to tilt toward the respective positive axis. In order for those values
              * to be reported, both tilt and orientation have to be populated on the StrokeInput.
              */
-            @JvmField public val TILT_Y_IN_RADIANS: Source = Source(4)
+            @JvmField public val TILT_Y_IN_RADIANS: Source = Source(3)
             /** Stylus orientation with values reported in the range [0, 2π). */
-            @JvmField public val ORIENTATION_IN_RADIANS: Source = Source(5)
+            @JvmField public val ORIENTATION_IN_RADIANS: Source = Source(4)
             /** Stylus orientation with values reported in the range (-π, π]. */
-            @JvmField public val ORIENTATION_ABOUT_ZERO_IN_RADIANS: Source = Source(6)
+            @JvmField public val ORIENTATION_ABOUT_ZERO_IN_RADIANS: Source = Source(5)
             /**
              * Pointer speed with values >= 0 in distance units per second, where one distance unit
              * is equal to the brush size.
              */
-            @JvmField public val SPEED_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND: Source = Source(7)
+            @JvmField public val SPEED_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND: Source = Source(6)
             /**
              * Signed x component of pointer velocity in distance units per second, where one
              * distance unit is equal to the brush size.
              */
             @JvmField
-            public val VELOCITY_X_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND: Source = Source(8)
+            public val VELOCITY_X_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND: Source = Source(7)
             /**
              * Signed y component of pointer velocity in distance units per second, where one
              * distance unit is equal to the brush size.
              */
             @JvmField
-            public val VELOCITY_Y_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND: Source = Source(9)
+            public val VELOCITY_Y_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND: Source = Source(8)
             /**
              * The angle of the stroke's current direction of travel in stroke space, normalized to
              * the range [0, 2π). A value of 0 indicates the direction of the positive X-axis in
              * stroke space; a value of π/2 indicates the direction of the positive Y-axis in stroke
              * space.
              */
-            @JvmField public val DIRECTION_IN_RADIANS: Source = Source(10)
+            @JvmField public val DIRECTION_IN_RADIANS: Source = Source(9)
             /**
              * The angle of the stroke's current direction of travel in stroke space, normalized to
              * the range (-π, π]. A value of 0 indicates the direction of the positive X-axis in
              * stroke space; a value of π/2 indicates the direction of the positive Y-axis in stroke
              * space.
              */
-            @JvmField public val DIRECTION_ABOUT_ZERO_IN_RADIANS: Source = Source(11)
+            @JvmField public val DIRECTION_ABOUT_ZERO_IN_RADIANS: Source = Source(10)
             /**
              * Signed x component of the normalized travel direction, with values in the range
              * [-1, 1].
              */
-            @JvmField public val NORMALIZED_DIRECTION_X: Source = Source(12)
+            @JvmField public val NORMALIZED_DIRECTION_X: Source = Source(11)
             /**
              * Signed y component of the normalized travel direction, with values in the range
              * [-1, 1].
              */
-            @JvmField public val NORMALIZED_DIRECTION_Y: Source = Source(13)
+            @JvmField public val NORMALIZED_DIRECTION_Y: Source = Source(12)
             /**
              * Distance traveled by the inputs of the current stroke, starting at 0 at the first
              * input, where one distance unit is equal to the brush size.
              */
-            @JvmField public val DISTANCE_TRAVELED_IN_MULTIPLES_OF_BRUSH_SIZE: Source = Source(14)
+            @JvmField public val DISTANCE_TRAVELED_IN_MULTIPLES_OF_BRUSH_SIZE: Source = Source(13)
             /**
              * The time elapsed, in seconds, from when the stroke started to when this part of the
              * stroke was drawn. The value remains fixed for any given part of the stroke once
              * drawn.
              */
-            @JvmField public val TIME_OF_INPUT_IN_SECONDS: Source = Source(15)
+            @JvmField public val TIME_OF_INPUT_IN_SECONDS: Source = Source(14)
             /**
              * The time elapsed, in millis, from when the stroke started to when this part of the
              * stroke was drawn. The value remains fixed for any given part of the stroke once
              * drawn.
              */
-            @JvmField public val TIME_OF_INPUT_IN_MILLIS: Source = Source(16)
+            @JvmField public val TIME_OF_INPUT_IN_MILLIS: Source = Source(15)
             /**
              * Distance traveled by the inputs of the current prediction, starting at 0 at the last
              * non-predicted input, where one distance unit is equal to the brush size. For cases
@@ -458,25 +446,25 @@
              * min of 0.
              */
             @JvmField
-            public val PREDICTED_DISTANCE_TRAVELED_IN_MULTIPLES_OF_BRUSH_SIZE: Source = Source(17)
+            public val PREDICTED_DISTANCE_TRAVELED_IN_MULTIPLES_OF_BRUSH_SIZE: Source = Source(16)
             /**
              * Elapsed time of the prediction, starting at 0 at the last non-predicted input. For
              * cases where prediction hasn't started yet, we don't return a negative value, but
              * clamp to a min of 0.
              */
-            @JvmField public val PREDICTED_TIME_ELAPSED_IN_SECONDS: Source = Source(18)
+            @JvmField public val PREDICTED_TIME_ELAPSED_IN_SECONDS: Source = Source(17)
             /**
              * Elapsed time of the prediction, starting at 0 at the last non-predicted input. For
              * cases where prediction hasn't started yet, we don't return a negative value, but
              * clamp to a min of 0.
              */
-            @JvmField public val PREDICTED_TIME_ELAPSED_IN_MILLIS: Source = Source(19)
+            @JvmField public val PREDICTED_TIME_ELAPSED_IN_MILLIS: Source = Source(18)
             /**
              * The distance left to be traveled from a given input to the current last input of the
              * stroke, where one distance unit is equal to the brush size. This value changes for
              * each input as the stroke is drawn.
              */
-            @JvmField public val DISTANCE_REMAINING_IN_MULTIPLES_OF_BRUSH_SIZE: Source = Source(20)
+            @JvmField public val DISTANCE_REMAINING_IN_MULTIPLES_OF_BRUSH_SIZE: Source = Source(19)
             /**
              * The amount of time that has elapsed, in seconds, since this part of the stroke was
              * drawn. This continues to increase even after all stroke inputs have completed, and
@@ -484,7 +472,7 @@
              * [sourceOutOfRangeBehavior] of [OutOfRange.CLAMP], to ensure that the animation will
              * eventually end.
              */
-            @JvmField public val TIME_SINCE_INPUT_IN_SECONDS: Source = Source(21)
+            @JvmField public val TIME_SINCE_INPUT_IN_SECONDS: Source = Source(20)
             /**
              * The amount of time that has elapsed, in millis, since this part of the stroke was
              * drawn. This continues to increase even after all stroke inputs have completed, and
@@ -492,28 +480,28 @@
              * [sourceOutOfRangeBehavior] of [OutOfRange.CLAMP], to ensure that the animation will
              * eventually end.
              */
-            @JvmField public val TIME_SINCE_INPUT_IN_MILLIS: Source = Source(22)
+            @JvmField public val TIME_SINCE_INPUT_IN_MILLIS: Source = Source(21)
             /**
              * Directionless pointer acceleration with values >= 0 in distance units per second
              * squared, where one distance unit is equal to the brush size.
              */
             @JvmField
             public val ACCELERATION_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED: Source =
-                Source(23)
+                Source(22)
             /**
              * Signed x component of pointer acceleration in distance units per second squared,
              * where one distance unit is equal to the brush size.
              */
             @JvmField
             public val ACCELERATION_X_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED: Source =
-                Source(24)
+                Source(23)
             /**
              * Signed y component of pointer acceleration in distance units per second squared,
              * where one distance unit is equal to the brush size.
              */
             @JvmField
             public val ACCELERATION_Y_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED: Source =
-                Source(25)
+                Source(24)
             /**
              * Pointer acceleration along the current direction of travel in distance units per
              * second squared, where one distance unit is equal to the brush size. A positive value
@@ -522,7 +510,7 @@
              */
             @JvmField
             public val ACCELERATION_FORWARD_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED: Source =
-                Source(26)
+                Source(25)
             /**
              * Pointer acceleration perpendicular to the current direction of travel in distance
              * units per second squared, where one distance unit is equal to the brush size. If the
@@ -533,27 +521,27 @@
              */
             @JvmField
             public val ACCELERATION_LATERAL_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED: Source =
-                Source(27)
+                Source(26)
             /**
              * The physical speed of the input pointer at the point in question, in centimeters per
              * second.
              */
-            @JvmField public val INPUT_SPEED_IN_CENTIMETERS_PER_SECOND: Source = Source(28)
+            @JvmField public val INPUT_SPEED_IN_CENTIMETERS_PER_SECOND: Source = Source(27)
             /**
              * Signed x component of the physical velocity of the input pointer at the point in
              * question, in centimeters per second.
              */
-            @JvmField public val INPUT_VELOCITY_X_IN_CENTIMETERS_PER_SECOND: Source = Source(29)
+            @JvmField public val INPUT_VELOCITY_X_IN_CENTIMETERS_PER_SECOND: Source = Source(28)
             /**
              * Signed y component of the physical velocity of the input pointer at the point in
              * question, in centimeters per second.
              */
-            @JvmField public val INPUT_VELOCITY_Y_IN_CENTIMETERS_PER_SECOND: Source = Source(30)
+            @JvmField public val INPUT_VELOCITY_Y_IN_CENTIMETERS_PER_SECOND: Source = Source(29)
             /**
              * The physical distance traveled by the input pointer from the start of the stroke
              * along the input path to the point in question, in centimeters.
              */
-            @JvmField public val INPUT_DISTANCE_TRAVELED_IN_CENTIMETERS: Source = Source(31)
+            @JvmField public val INPUT_DISTANCE_TRAVELED_IN_CENTIMETERS: Source = Source(30)
             /**
              * The physical distance that the input pointer would have to travel from its actual
              * last real position along its predicted path to reach the predicted point in question,
@@ -561,25 +549,25 @@
              * value of zero.
              */
             @JvmField
-            public val PREDICTED_INPUT_DISTANCE_TRAVELED_IN_CENTIMETERS: Source = Source(32)
+            public val PREDICTED_INPUT_DISTANCE_TRAVELED_IN_CENTIMETERS: Source = Source(31)
             /**
              * The directionless physical acceleration of the input pointer at the point in
              * question, with values >= 0, in centimeters per second squared.
              */
             @JvmField
-            public val INPUT_ACCELERATION_IN_CENTIMETERS_PER_SECOND_SQUARED: Source = Source(33)
+            public val INPUT_ACCELERATION_IN_CENTIMETERS_PER_SECOND_SQUARED: Source = Source(32)
             /**
              * Signed x component of the physical acceleration of the input pointer, in centimeters
              * per second squared.
              */
             @JvmField
-            public val INPUT_ACCELERATION_X_IN_CENTIMETERS_PER_SECOND_SQUARED: Source = Source(34)
+            public val INPUT_ACCELERATION_X_IN_CENTIMETERS_PER_SECOND_SQUARED: Source = Source(33)
             /**
              * Signed y component of the physical acceleration of the input pointer, in centimeters
              * per second squared.
              */
             @JvmField
-            public val INPUT_ACCELERATION_Y_IN_CENTIMETERS_PER_SECOND_SQUARED: Source = Source(35)
+            public val INPUT_ACCELERATION_Y_IN_CENTIMETERS_PER_SECOND_SQUARED: Source = Source(34)
             /**
              * The physical acceleration of the input pointer along its current direction of travel
              * at the point in question, in centimeters per second squared. A positive value
@@ -588,7 +576,7 @@
              */
             @JvmField
             public val INPUT_ACCELERATION_FORWARD_IN_CENTIMETERS_PER_SECOND_SQUARED: Source =
-                Source(36)
+                Source(35)
             /**
              * The physical acceleration of the input pointer perpendicular to its current direction
              * of travel at the point in question, in centimeters per second squared. If the X- and
@@ -599,7 +587,7 @@
              */
             @JvmField
             public val INPUT_ACCELERATION_LATERAL_IN_CENTIMETERS_PER_SECOND_SQUARED: Source =
-                Source(37)
+                Source(36)
             private const val PREFIX = "BrushBehavior.Source."
         }
     }
@@ -1002,7 +990,7 @@
         }
 
         /** Appends a native `BrushBehavior::SourceNode` to a native brush behavior struct. */
-        // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+        @UsedByNative
         private external fun nativeAppendSourceNode(
             nativeBehaviorPointer: Long,
             source: Int,
@@ -1032,10 +1020,8 @@
         override fun hashCode(): Int = value.hashCode()
 
         /** Appends a native `BrushBehavior::ConstantNode` to a native brush behavior struct. */
-        private external fun nativeAppendConstantNode(
-            nativeBehaviorPointer: Long,
-            value: Float
-        ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+        @UsedByNative
+        private external fun nativeAppendConstantNode(nativeBehaviorPointer: Long, value: Float)
     }
 
     /**
@@ -1067,7 +1053,7 @@
         /**
          * Appends a native `BrushBehavior::FallbackFilterNode` to a native brush behavior struct.
          */
-        // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+        @UsedByNative
         private external fun nativeAppendFallbackFilterNode(
             nativeBehaviorPointer: Long,
             isFallbackFor: Int,
@@ -1117,7 +1103,7 @@
         /**
          * Appends a native `BrushBehavior::ToolTypeFilterNode` to a native brush behavior struct.
          */
-        // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+        @UsedByNative
         private external fun nativeAppendToolTypeFilterNode(
             nativeBehaviorPointer: Long,
             mouseEnabled: Boolean,
@@ -1166,7 +1152,7 @@
         }
 
         /** Appends a native `BrushBehavior::DampingNode` to a native brush behavior struct. */
-        // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+        @UsedByNative
         private external fun nativeAppendDampingNode(
             nativeBehaviorPointer: Long,
             dampingSource: Int,
@@ -1230,7 +1216,7 @@
          * Appends a native `BrushBehavior::ResponseNode` with response curve of type
          * [EasingFunction.Predefined] to a native brush behavior struct.
          */
-        // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+        @UsedByNative
         private external fun nativeAppendResponseNodePredefined(
             nativeBehaviorPointer: Long,
             predefinedResponseCurve: Int,
@@ -1240,7 +1226,7 @@
          * Appends a native `BrushBehavior::ResponseNode` with response curve of type
          * [EasingFunction.CubicBezier] to a native brush behavior struct.
          */
-        // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+        @UsedByNative
         private external fun nativeAppendResponseNodeCubicBezier(
             nativeBehaviorPointer: Long,
             cubicBezierX1: Float,
@@ -1253,7 +1239,7 @@
          * Appends a native `BrushBehavior::ResponseNode` with response curve of type
          * [EasingFunction.Steps] to a native brush behavior struct.
          */
-        // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+        @UsedByNative
         private external fun nativeAppendResponseNodeSteps(
             nativeBehaviorPointer: Long,
             stepsCount: Int,
@@ -1264,7 +1250,7 @@
          * Appends a native `BrushBehavior::ResponseNode` with response curve of type
          * [EasingFunction.Linear] to a native brush behavior struct.
          */
-        // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+        @UsedByNative
         private external fun nativeAppendResponseNodeLinear(
             nativeBehaviorPointer: Long,
             points: FloatArray,
@@ -1301,10 +1287,8 @@
         }
 
         /** Appends a native `BrushBehavior::BinaryOpNode` to a native brush behavior struct. */
-        private external fun nativeAppendBinaryOpNode(
-            nativeBehaviorPointer: Long,
-            operation: Int
-        ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+        @UsedByNative
+        private external fun nativeAppendBinaryOpNode(nativeBehaviorPointer: Long, operation: Int)
     }
 
     /**
@@ -1349,7 +1333,7 @@
         /**
          * Appends a native `BrushBehavior::InterpolationNode` to a native brush behavior struct.
          */
-        // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+        @UsedByNative
         private external fun nativeAppendInterpolationNode(
             nativeBehaviorPointer: Long,
             interpolation: Int,
@@ -1411,7 +1395,7 @@
         }
 
         /** Appends a native `BrushBehavior::TargetNode` to a native brush behavior struct. */
-        // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+        @UsedByNative
         private external fun nativeAppendTargetNode(
             nativeBehaviorPointer: Long,
             target: Int,
diff --git a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushCoat.kt b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushCoat.kt
index 1582b57..f36d1c8 100644
--- a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushCoat.kt
+++ b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushCoat.kt
@@ -18,6 +18,7 @@
 
 import androidx.annotation.RestrictTo
 import androidx.ink.nativeloader.NativeLoader
+import androidx.ink.nativeloader.UsedByNative
 import java.util.Collections.unmodifiableList
 import kotlin.jvm.JvmOverloads
 import kotlin.jvm.JvmStatic
@@ -141,16 +142,14 @@
     }
 
     /** Create underlying native object and return reference for all subsequent native calls. */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeCreateBrushCoat(
         tipNativePointers: LongArray,
         paintNativePointer: Long,
     ): Long
 
     /** Release the underlying memory allocated in [nativeCreateBrushCoat]. */
-    private external fun nativeFreeBrushCoat(
-        nativePointer: Long
-    ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun nativeFreeBrushCoat(nativePointer: Long)
 
     // Companion object gets initialized before anything else.
     public companion object {
diff --git a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushFamily.kt b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushFamily.kt
index 66b2da9..2524569 100644
--- a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushFamily.kt
+++ b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushFamily.kt
@@ -18,6 +18,7 @@
 
 import androidx.annotation.RestrictTo
 import androidx.ink.nativeloader.NativeLoader
+import androidx.ink.nativeloader.UsedByNative
 import java.util.Collections.unmodifiableList
 import kotlin.jvm.JvmField
 import kotlin.jvm.JvmOverloads
@@ -186,7 +187,7 @@
     }
 
     /** Create underlying native object and return reference for all subsequent native calls. */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeCreateBrushFamily(
         coatNativePointers: LongArray,
         uri: String?,
@@ -194,9 +195,7 @@
     ): Long
 
     /** Release the underlying memory allocated in [nativeCreateBrushFamily]. */
-    private external fun nativeFreeBrushFamily(
-        nativePointer: Long
-    ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun nativeFreeBrushFamily(nativePointer: Long)
 
     // Companion object gets initialized before anything else.
     public companion object {
diff --git a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushPaint.kt b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushPaint.kt
index 4e3a1ab..563d452 100644
--- a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushPaint.kt
+++ b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushPaint.kt
@@ -20,6 +20,7 @@
 import androidx.annotation.RestrictTo
 import androidx.ink.geometry.AngleRadiansFloat
 import androidx.ink.nativeloader.NativeLoader
+import androidx.ink.nativeloader.UsedByNative
 import java.util.Collections.unmodifiableList
 import kotlin.Suppress
 import kotlin.jvm.JvmField
@@ -74,22 +75,18 @@
     }
 
     /** Create underlying native object and return reference for all subsequent native calls. */
-    private external fun nativeCreateBrushPaint(
-        textureLayersCount: Int
-    ): Long // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun nativeCreateBrushPaint(textureLayersCount: Int): Long
 
     /**
      * Appends a texture layer to a *mutable* C++ BrushPaint object as referenced by
      * [nativePointer]. Only call during `init{}` so to keep this BrushPaint object immutable after
      * construction and equivalent across Kotlin and C++.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeAppendTextureLayer(nativePointer: Long, textureLayerPointer: Long)
 
     /** Release the underlying memory allocated in [nativeCreateBrushPaint]. */
-    private external fun nativeFreeBrushPaint(
-        nativePointer: Long
-    ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun nativeFreeBrushPaint(nativePointer: Long)
 
     /** Specification of how the texture should apply to the stroke. */
     public class TextureMapping private constructor(@JvmField internal val value: Int) {
@@ -639,7 +636,7 @@
                 )
         }
 
-        // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+        @UsedByNative
         private external fun nativeCreateTextureLayer(
             colorTextureUri: String,
             sizeX: Float,
@@ -655,9 +652,7 @@
         ): Long
 
         /** Release the underlying memory allocated in [nativeCreateTextureLayer]. */
-        private external fun nativeFreeTextureLayer(
-            nativePointer: Long
-        ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+        @UsedByNative private external fun nativeFreeTextureLayer(nativePointer: Long)
 
         // To be extended by extension methods.
         public companion object
diff --git a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushTip.kt b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushTip.kt
index e226874..69e1606 100644
--- a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushTip.kt
+++ b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/BrushTip.kt
@@ -22,6 +22,7 @@
 import androidx.ink.geometry.Angle
 import androidx.ink.geometry.AngleRadiansFloat
 import androidx.ink.nativeloader.NativeLoader
+import androidx.ink.nativeloader.UsedByNative
 import java.util.Collections.unmodifiableList
 import kotlin.jvm.JvmStatic
 import kotlin.jvm.JvmSynthetic
@@ -342,7 +343,7 @@
     }
 
     /** Create underlying native object and return reference for all subsequent native calls. */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeCreateBrushTip(
         scaleX: Float,
         scaleY: Float,
@@ -361,13 +362,11 @@
      * Only call during init{} so to keep this BrushTip object immutable after construction and
      * equivalent across Kotlin and C++.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeAppendBehavior(tipNativePointer: Long, behaviorNativePointer: Long)
 
     /** Release the underlying memory allocated in [nativeCreateBrushTip]. */
-    private external fun nativeFreeBrushTip(
-        nativePointer: Long
-    ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun nativeFreeBrushTip(nativePointer: Long)
 
     // Companion object gets initialized before anything else.
     public companion object {
diff --git a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/InputToolType.kt b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/InputToolType.kt
index db2f8f5..d0c8086 100644
--- a/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/InputToolType.kt
+++ b/ink/ink-brush/src/jvmAndroidMain/kotlin/androidx/ink/brush/InputToolType.kt
@@ -17,6 +17,7 @@
 package androidx.ink.brush
 
 import androidx.annotation.RestrictTo
+import androidx.ink.nativeloader.UsedByNative
 import kotlin.jvm.JvmName
 import kotlin.jvm.JvmStatic
 
@@ -25,16 +26,12 @@
  * [BrushBehavior] to define when a behavior is applicable.
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-
-// TODO: b/355248266 - @UsedByNative("stroke_input_jni_helper.cc") must go in Proguard config file
-// instead.
+@UsedByNative
 public class InputToolType
 private constructor(
+    @UsedByNative
     @JvmField
-    @field:RestrictTo(
-        RestrictTo.Scope.LIBRARY_GROUP
-    ) // NonPublicApi // TODO: b/355248266 - @UsedByNative("stroke_input_jni_helper.cc") must go in
-    // Proguard config file instead.
+    @field:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
     public val value: Int
 ) {
 
@@ -64,8 +61,7 @@
          */
         @JvmStatic
         @JvmName("from")
-        // TODO: b/355248266 - @UsedByNative("stroke_input_jni_helper.cc") must go in Proguard
-        // config file instead.
+        @UsedByNative
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         public fun from(value: Int): InputToolType {
             return when (value) {
diff --git a/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushBehaviorTest.kt b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushBehaviorTest.kt
index a7d83ad..806a456 100644
--- a/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushBehaviorTest.kt
+++ b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushBehaviorTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.ink.brush
 
+import androidx.ink.nativeloader.UsedByNative
 import com.google.common.truth.Truth.assertThat
 import kotlin.IllegalArgumentException
 import kotlin.test.assertFailsWith
@@ -31,7 +32,6 @@
     fun sourceConstants_areDistinct() {
         val list =
             listOf<BrushBehavior.Source>(
-                BrushBehavior.Source.CONSTANT_ZERO,
                 BrushBehavior.Source.NORMALIZED_PRESSURE,
                 BrushBehavior.Source.TILT_IN_RADIANS,
                 BrushBehavior.Source.TILT_X_IN_RADIANS,
@@ -96,8 +96,6 @@
 
     @Test
     fun sourceToString_returnsCorrectString() {
-        assertThat(BrushBehavior.Source.CONSTANT_ZERO.toString())
-            .isEqualTo("BrushBehavior.Source.CONSTANT_ZERO")
         assertThat(BrushBehavior.Source.NORMALIZED_PRESSURE.toString())
             .isEqualTo("BrushBehavior.Source.NORMALIZED_PRESSURE")
         assertThat(BrushBehavior.Source.TILT_IN_RADIANS.toString())
@@ -1504,7 +1502,7 @@
      * Kotlin BrushBehavior's JNI-created C++ counterpart is equivalent to the expected C++
      * BrushBehavior.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun matchesNativeStepBehavior(
         nativePointerToActualBrushBehavior: Long
     ): Boolean
@@ -1514,7 +1512,7 @@
      * of the Kotlin BrushBehavior's JNI-created C++ counterpart is equivalent to the expected C++
      * BrushBehavior.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun matchesNativePredefinedBehavior(
         nativePointerToActualBrushBehavior: Long
     ): Boolean
@@ -1524,7 +1522,7 @@
      * Kotlin BrushBehavior's JNI-created C++ counterpart is equivalent to the expected C++
      * BrushBehavior.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun matchesNativeCubicBezierBehavior(
         nativePointerToActualBrushBehavior: Long
     ): Boolean
@@ -1533,7 +1531,7 @@
      * Creates an expected C++ Linear BrushBehavior and returns true if every property of the Kotlin
      * BrushBehavior's JNI-created C++ counterpart is equivalent to the expected C++ BrushBehavior.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun matchesNativeLinearBehavior(
         nativePointerToActualBrushBehavior: Long
     ): Boolean
diff --git a/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushCoatTest.kt b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushCoatTest.kt
index a3504a3..93b773a 100644
--- a/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushCoatTest.kt
+++ b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushCoatTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.ink.brush
 
+import androidx.ink.nativeloader.UsedByNative
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -97,18 +98,15 @@
      * Creates an expected C++ BrushCoat with defaults and returns true if every property of the
      * Kotlin BrushCoat's JNI-created C++ counterpart is equivalent to the expected C++ BrushCoat.
      */
-    private external fun matchesDefaultCoat(
-        brushCoatNativePointer: Long
-    ): Boolean // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun matchesDefaultCoat(brushCoatNativePointer: Long): Boolean
 
     /**
      * Creates an expected C++ BrushCoat with custom values and returns true if every property of
      * the Kotlin BrushCoat's JNI-created C++ counterpart is equivalent to the expected C++
      * BrushCoat.
      */
-    private external fun matchesMultiBehaviorTipCoat(
-        brushCoatNativePointer: Long
-    ): Boolean // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
+    private external fun matchesMultiBehaviorTipCoat(brushCoatNativePointer: Long): Boolean
 
     /** Brush behavior with every field different from default values. */
     private val customBehavior =
diff --git a/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushFamilyTest.kt b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushFamilyTest.kt
index 0d368e1..540657d 100644
--- a/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushFamilyTest.kt
+++ b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushFamilyTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.ink.brush
 
+import androidx.ink.nativeloader.UsedByNative
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.assertFailsWith
 import org.junit.Test
@@ -138,18 +139,15 @@
      * Kotlin BrushFamily's JNI-created C++ counterpart is equivalent to the expected C++
      * BrushFamily.
      */
-    private external fun matchesDefaultFamily(
-        brushFamilyNativePointer: Long
-    ): Boolean // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun matchesDefaultFamily(brushFamilyNativePointer: Long): Boolean
 
     /**
      * Creates an expected C++ BrushFamily with custom values and returns true if every property of
      * the Kotlin BrushFamily's JNI-created C++ counterpart is equivalent to the expected C++
      * BrushFamily.
      */
-    private external fun matchesMultiBehaviorTipFamily(
-        brushFamilyNativePointer: Long
-    ): Boolean // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
+    private external fun matchesMultiBehaviorTipFamily(brushFamilyNativePointer: Long): Boolean
 
     private val customUri = "/brush-family:inkpen:1"
 
diff --git a/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushPaintTest.kt b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushPaintTest.kt
index 1982d5f..3d2108b 100644
--- a/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushPaintTest.kt
+++ b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushPaintTest.kt
@@ -17,6 +17,7 @@
 package androidx.ink.brush
 
 import androidx.ink.geometry.Angle
+import androidx.ink.nativeloader.UsedByNative
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.assertFailsWith
 import org.junit.Test
@@ -473,9 +474,8 @@
 
     // endregion
 
-    private external fun matchesNativeCustomPaint(
-        brushPaintNativePointer: Long
-    ): Boolean // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
+    private external fun matchesNativeCustomPaint(brushPaintNativePointer: Long): Boolean
 
     private fun makeTestTextureUri(version: Int = 0) =
         "ink://ink/texture:test-texture" + if (version == 0) "" else ":" + version
diff --git a/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushTest.kt b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushTest.kt
index ca8c4d4..348c6bd 100644
--- a/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushTest.kt
+++ b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushTest.kt
@@ -19,6 +19,7 @@
 import androidx.ink.brush.color.Color
 import androidx.ink.brush.color.colorspace.ColorSpaces
 import androidx.ink.brush.color.toArgb
+import androidx.ink.nativeloader.UsedByNative
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.assertFailsWith
 import org.junit.Test
@@ -355,17 +356,13 @@
      * property of the Kotlin Brush's JNI-created C++ counterpart is equivalent to the expected C++
      * Brush.
      */
-    private external fun matchesDefaultBrush(
-        actualBrushNativePointer: Long
-    ): Boolean // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun matchesDefaultBrush(actualBrushNativePointer: Long): Boolean
 
     /**
      * Creates an expected C++ Brush with custom values and returns true if every property of the
      * Kotlin Brush's JNI-created C++ counterpart is equivalent to the expected C++ Brush.
      */
-    private external fun matchesCustomBrush(
-        actualBrushNativePointer: Long
-    ): Boolean // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun matchesCustomBrush(actualBrushNativePointer: Long): Boolean
 
     /** Brush with every field different from default values. */
     private fun buildTestBrush(): Brush =
diff --git a/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushTipTest.kt b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushTipTest.kt
index 2e54338..8926445 100644
--- a/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushTipTest.kt
+++ b/ink/ink-brush/src/jvmAndroidTest/kotlin/androidx/ink/brush/BrushTipTest.kt
@@ -17,6 +17,7 @@
 package androidx.ink.brush
 
 import androidx.ink.geometry.Angle
+import androidx.ink.nativeloader.UsedByNative
 import com.google.common.truth.Truth.assertThat
 import kotlin.IllegalArgumentException
 import kotlin.test.assertFailsWith
@@ -364,14 +365,14 @@
      * Creates an expected C++ BrushTip with no behaviors and returns true if every property of the
      * Kotlin BrushTip's JNI-created C++ counterpart is equivalent to the expected C++ BrushTip.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun matchesNativeNoBehaviorTip(nativePointerToActualBrushTip: Long): Boolean
 
     /**
      * Creates an expected C++ BrushTip with a single behavior and returns true if every property of
      * the Kotlin BrushTip's JNI-created C++ counterpart is equivalent to the expected C++ BrushTip.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun matchesNativeSingleBehaviorTip(
         nativePointerToActualBrushTip: Long
     ): Boolean
@@ -381,6 +382,6 @@
      * of the Kotlin BrushTip's JNI-created C++ counterpart is equivalent to the expected C++
      * BrushTip.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun matchesNativeMultiBehaviorTip(nativePointerToActualBrushTip: Long): Boolean
 }
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/AffineTransformHelper.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/AffineTransformHelper.kt
index 600a3b2..dc70836 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/AffineTransformHelper.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/AffineTransformHelper.kt
@@ -17,6 +17,7 @@
 package androidx.ink.geometry
 
 import androidx.ink.nativeloader.NativeLoader
+import androidx.ink.nativeloader.UsedByNative
 
 /** Helper functions for AffineTransform. */
 internal object AffineTransformHelper {
@@ -25,7 +26,7 @@
         NativeLoader.load()
     }
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun nativeApplyParallelogram(
         affineTransformA: Float,
         affineTransformB: Float,
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Angle.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Angle.kt
index c2db769..90ad199 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Angle.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Angle.kt
@@ -18,6 +18,7 @@
 
 import androidx.annotation.FloatRange
 import androidx.ink.nativeloader.NativeLoader
+import androidx.ink.nativeloader.UsedByNative
 
 /**
  * A utility for working with a signed angle. A positive value represents rotation from the positive
@@ -72,11 +73,7 @@
     @AngleRadiansFloat
     public val FULL_TURN_RADIANS: Float = FULL_TURN_RADIANS_DOUBLE.toFloat()
 
-    private external fun nativeNormalized(
-        radians: Float
-    ): Float // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun nativeNormalized(radians: Float): Float
 
-    private external fun nativeNormalizedAboutZero(
-        radians: Float
-    ): Float // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun nativeNormalizedAboutZero(radians: Float): Float
 }
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/BoxAccumulator.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/BoxAccumulator.kt
index dde4d23..44db8e7 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/BoxAccumulator.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/BoxAccumulator.kt
@@ -19,13 +19,13 @@
 import androidx.annotation.FloatRange
 import androidx.annotation.RestrictTo
 import androidx.ink.nativeloader.NativeLoader
+import androidx.ink.nativeloader.UsedByNative
 
 /**
  * A helper class for accumulating the minimum bounding boxes of zero or more geometry objects. In
  * colloquial terms, this can be used to find the smallest Box that contains a set of objects.
  */
-// TODO: b/355248266 - @UsedByNative("envelope_jni_helper.cc") must go in Proguard config file
-// instead.
+@UsedByNative
 public class BoxAccumulator {
     /**
      * The bounds, which are valid only if [hasBounds] is `true`. When [hasBounds] is `false`, this
@@ -79,9 +79,7 @@
     }
 
     /** Reset this object to have no bounds. Returns the same instance to chain function calls. */
-    // TODO: b/355248266 - @UsedByNative("envelope_jni_helper.cc") must go in Proguard config file
-    // instead.
-
+    @UsedByNative
     public fun reset(): BoxAccumulator {
         hasBounds = false
         _bounds.setXBounds(Float.NaN, Float.NaN).setYBounds(Float.NaN, Float.NaN)
@@ -247,9 +245,7 @@
      *
      * @return `this`
      */
-    // TODO: b/355248266 - @UsedByNative("envelope_jni_helper.cc") must go in Proguard config file
-    // instead.
-
+    @UsedByNative
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
     public fun overwriteFrom(x1: Float, y1: Float, x2: Float, y2: Float): BoxAccumulator {
         hasBounds = true
@@ -290,7 +286,7 @@
      * Helper method to construct a native C++ [Envelope] and [Segment], add the native [Segment] to
      * the native [Envelope], and update [output] using the result.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun nativeAddSegment(
         envelopeHasBounds: Boolean,
         envelopeBoundsXMin: Float,
@@ -308,7 +304,7 @@
      * Helper method to construct a native C++ [Envelope] and [Triangle], add the native [Triangle]
      * to the native [Envelope], and update [output] using the result.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun nativeAddTriangle(
         envelopeHasBounds: Boolean,
         envelopeBoundsXMin: Float,
@@ -328,7 +324,7 @@
      * Helper method to construct a native C++ [Envelope] and [Parallelogram], add the native
      * [Parallelogram] to the native [Envelope], and update [output] using the result.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun nativeAddParallelogram(
         envelopeHasBounds: Boolean,
         envelopeBoundsXMin: Float,
@@ -348,7 +344,7 @@
      * Helper method to construct a native C++ [Envelope] and [Point], add the native [Point] to the
      * native [Envelope], and update [output] using the result.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun nativeAddPoint(
         envelopeHasBounds: Boolean,
         envelopeBoundsXMin: Float,
@@ -364,7 +360,7 @@
      * Helper method to construct a native C++ [Envelope] using [this], add the optional box to the
      * native [Envelope], and update [output] using the result.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun nativeAddOptionalBox(
         envelopeHasBounds: Boolean,
         envelopeBoundsXMin: Float,
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/BoxHelper.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/BoxHelper.kt
index 68f902b..992e3b9 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/BoxHelper.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/BoxHelper.kt
@@ -17,6 +17,7 @@
 package androidx.ink.geometry
 
 import androidx.ink.nativeloader.NativeLoader
+import androidx.ink.nativeloader.UsedByNative
 
 /** Helper functions for MutableBox and ImmutableBox. */
 internal object BoxHelper {
@@ -25,7 +26,7 @@
         NativeLoader.load()
     }
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun nativeCenter(
         rectXMin: Float,
         rectYMin: Float,
@@ -34,7 +35,7 @@
         out: MutableVec,
     )
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun nativeContainsPoint(
         rectXMin: Float,
         rectYMin: Float,
@@ -44,7 +45,7 @@
         pointY: Float,
     ): Boolean
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun nativeContainsBox(
         rectXMin: Float,
         rectYMin: Float,
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Intersection.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Intersection.kt
index fcdf2f5..697816f 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Intersection.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Intersection.kt
@@ -17,6 +17,7 @@
 package androidx.ink.geometry
 
 import androidx.ink.nativeloader.NativeLoader
+import androidx.ink.nativeloader.UsedByNative
 
 /**
  * Contains functions for intersection of ink geometry classes. For Kotlin callers, these are
@@ -613,7 +614,7 @@
         meshToParallelogram: AffineTransform,
     ): Boolean = parallelogram.intersects(this, meshToParallelogram)
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeVecSegmentIntersects(
         vecX: Float,
         vecY: Float,
@@ -623,7 +624,7 @@
         segmentEndY: Float,
     ): Boolean
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeVecTriangleIntersects(
         vecX: Float,
         vecY: Float,
@@ -635,7 +636,7 @@
         triangleP2Y: Float,
     ): Boolean
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeVecParallelogramIntersects(
         vecX: Float,
         vecY: Float,
@@ -647,7 +648,7 @@
         parallelogramShearFactor: Float,
     ): Boolean
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeVecBoxIntersects(
         vecX: Float,
         vecY: Float,
@@ -657,7 +658,7 @@
         boxYMax: Float,
     ): Boolean
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeSegmentSegmentIntersects(
         segment1StartX: Float,
         segment1StartY: Float,
@@ -669,7 +670,7 @@
         segment2EndY: Float,
     ): Boolean
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeSegmentTriangleIntersects(
         segmentStartX: Float,
         segmentStartY: Float,
@@ -683,7 +684,7 @@
         triangleP2Y: Float,
     ): Boolean
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeSegmentBoxIntersects(
         segmentStartX: Float,
         segmentStartY: Float,
@@ -695,7 +696,7 @@
         boxYMax: Float,
     ): Boolean
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeSegmentParallelogramIntersects(
         segmentStartX: Float,
         segmentStartY: Float,
@@ -709,7 +710,7 @@
         parallelogramShearFactor: Float,
     ): Boolean
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeTriangleTriangleIntersects(
         triangle1P0X: Float,
         triangle1P0Y: Float,
@@ -725,7 +726,7 @@
         triangle2P2Y: Float,
     ): Boolean
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeTriangleBoxIntersects(
         triangleP0X: Float,
         triangleP0Y: Float,
@@ -739,7 +740,7 @@
         boxYMax: Float,
     ): Boolean
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeTriangleParallelogramIntersects(
         triangleP0X: Float,
         triangleP0Y: Float,
@@ -755,7 +756,7 @@
         parallelogramShearFactor: Float,
     ): Boolean
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeBoxBoxIntersects(
         box1XMin: Float,
         box1YMin: Float,
@@ -767,7 +768,7 @@
         box2YMax: Float,
     ): Boolean
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeBoxParallelogramIntersects(
         boxXMin: Float,
         boxYMin: Float,
@@ -781,7 +782,7 @@
         parallelogramShearFactor: Float,
     ): Boolean
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeParallelogramParallelogramIntersects(
         parallelogram1CenterX: Float,
         parallelogram1CenterY: Float,
@@ -797,7 +798,7 @@
         parallelogram2ShearFactor: Float,
     ): Boolean
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeMeshVecIntersects(
         nativeMeshAddress: Long,
         vecX: Float,
@@ -810,7 +811,7 @@
         meshToVecF: Float,
     ): Boolean
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeMeshSegmentIntersects(
         nativeMeshAddress: Long,
         segmentStartX: Float,
@@ -825,7 +826,7 @@
         meshToSegmentF: Float,
     ): Boolean
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeMeshTriangleIntersects(
         nativeMeshAddress: Long,
         triangleP0X: Float,
@@ -842,7 +843,7 @@
         meshToTriangleF: Float,
     ): Boolean
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeMeshBoxIntersects(
         nativeMeshAddress: Long,
         boxXMin: Float,
@@ -857,7 +858,7 @@
         meshToBoxF: Float,
     ): Boolean
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeMeshParallelogramIntersects(
         nativeMeshAddress: Long,
         parallelogramCenterX: Float,
@@ -874,7 +875,7 @@
         meshToParallelogramF: Float,
     ): Boolean
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeMeshModeledShapeIntersects(
         thisModeledShapeAddress: Long,
         otherModeledShapeAddress: Long,
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Mesh.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Mesh.kt
index e3959af..36b51e8 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Mesh.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Mesh.kt
@@ -20,6 +20,7 @@
 import androidx.annotation.RestrictTo
 import androidx.annotation.VisibleForTesting
 import androidx.ink.nativeloader.NativeLoader
+import androidx.ink.nativeloader.UsedByNative
 import java.nio.ByteBuffer
 import java.nio.ShortBuffer
 import java.util.Collections
@@ -170,47 +171,30 @@
         NativeLoader.load()
     }
 
-    external fun freeNative(
-        nativeAddress: Long
-    ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun freeNative(nativeAddress: Long)
 
     /**
      * Returns a direct [ByteBuffer] wrapped around the contents of `ink::Mesh::RawVertexData`. It
      * will be writeable, so be sure to only expose a read-only wrapper of it.
      */
-    external fun createRawVertexBuffer(
-        nativeAddress: Long
-    ): ByteBuffer? // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun createRawVertexBuffer(nativeAddress: Long): ByteBuffer?
 
-    external fun getVertexStride(
-        nativeAddress: Long
-    ): Int // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun getVertexStride(nativeAddress: Long): Int
 
-    external fun getVertexCount(
-        nativeAddress: Long
-    ): Int // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun getVertexCount(nativeAddress: Long): Int
 
     /** Like [createRawVertexBuffer], but with `ink::Mesh::RawIndexData`. */
-    external fun createRawTriangleIndexBuffer(
-        nativeAddress: Long
-    ): ByteBuffer? // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun createRawTriangleIndexBuffer(nativeAddress: Long): ByteBuffer?
 
-    external fun getTriangleCount(
-        nativeAddress: Long
-    ): Int // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun getTriangleCount(nativeAddress: Long): Int
 
-    external fun getAttributeCount(
-        nativeAddress: Long
-    ): Int // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun getAttributeCount(nativeAddress: Long): Int
 
     /**
      * Sets the given [BoxAccumulator] to the bounds of the mesh, including resetting the object if
      * the mesh has no bounds.
      */
-    external fun fillBounds(
-        nativeAddress: Long,
-        boxAccumulator: BoxAccumulator
-    ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun fillBounds(nativeAddress: Long, boxAccumulator: BoxAccumulator)
 
     /**
      * Set the given [offsets] and [scales] arrays (each of which must have at least
@@ -221,7 +205,7 @@
      *   number of [ComponentUnpackingParams] in the [MeshAttributeUnpackingParams] that should be
      *   created for this attribute.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun fillAttributeUnpackingParams(
         nativeAddress: Long,
         attributeIndex: Int,
@@ -232,17 +216,10 @@
     /**
      * Return the address of a newly allocated copy of the `ink::MeshFormat` belonging to this mesh.
      */
-    external fun allocCopyOfFormat(
-        nativeAddress: Long
-    ): Long // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun allocCopyOfFormat(nativeAddress: Long): Long
 
-    external fun fillPosition(
-        nativeAddress: Long,
-        vertexIndex: Int,
-        outPosition: MutableVec
-    ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
+    external fun fillPosition(nativeAddress: Long, vertexIndex: Int, outPosition: MutableVec)
 
-    @VisibleForTesting
-    external fun allocNativeNewEmptyMesh():
-        Long // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @VisibleForTesting @UsedByNative external fun allocNativeNewEmptyMesh(): Long
 }
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MeshFormat.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MeshFormat.kt
index aaa0a0f..45e42e5 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MeshFormat.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MeshFormat.kt
@@ -18,6 +18,7 @@
 
 import androidx.annotation.RestrictTo
 import androidx.ink.nativeloader.NativeLoader
+import androidx.ink.nativeloader.UsedByNative
 
 /** Determines how the raw data of a [Mesh] is represented. */
 @Suppress("NotCloseable") // Finalize is only used to free the native peer.
@@ -52,21 +53,19 @@
         nativeFree(nativeAddress)
     }
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeIsPackedEquivalent(
         firstNativeAddress: Long,
         secondNativeAddress: Long,
     ): Boolean
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeIsUnpackedEquivalent(
         firstNativeAddress: Long,
         secondNativeAddress: Long,
     ): Boolean
 
-    private external fun nativeFree(
-        nativeAddress: Long
-    ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun nativeFree(nativeAddress: Long)
 
     public companion object {
         init {
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableParallelogram.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableParallelogram.kt
index 59f8453..0995058 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableParallelogram.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableParallelogram.kt
@@ -18,6 +18,7 @@
 
 import androidx.annotation.FloatRange
 import androidx.annotation.RestrictTo
+import androidx.ink.nativeloader.UsedByNative
 
 /**
  * Mutable parallelogram (i.e. a quadrilateral with parallel sides), defined by its [center],
@@ -67,9 +68,7 @@
     public constructor() : this(MutableVec(), 0f, 0f, Angle.ZERO, 0f)
 
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // NonPublicApi
-
-    // TODO: b/355248266 - @UsedByNative("parallelogram_jni_helper.cc") must go in Proguard config
-    // file instead.
+    @UsedByNative
     public fun setCenterDimensionsRotationAndShear(
         centerX: Float,
         centerY: Float,
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableVec.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableVec.kt
index 81226b1..ad6d41d 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableVec.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/MutableVec.kt
@@ -17,6 +17,7 @@
 package androidx.ink.geometry
 
 import androidx.annotation.RestrictTo
+import androidx.ink.nativeloader.UsedByNative
 import kotlin.math.cos
 import kotlin.math.sin
 
@@ -31,12 +32,8 @@
  * immutable alternative.
  */
 public class MutableVec(
-    override var x:
-        Float, // TODO: b/355248266 - @set:UsedByNative("vec_jni_helper.cc") must go in Proguard
-    // config file instead.
-    override var y:
-        Float, // TODO: b/355248266 - @set:UsedByNative("vec_jni_helper.cc") must go in Proguard
-    // config file instead.
+    @set:UsedByNative override var x: Float,
+    @set:UsedByNative override var y: Float,
 ) : Vec() {
 
     public constructor() : this(0F, 0F)
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/PartitionedMesh.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/PartitionedMesh.kt
index c6d218a..9b2d871 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/PartitionedMesh.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/PartitionedMesh.kt
@@ -22,6 +22,7 @@
 import androidx.annotation.VisibleForTesting
 import androidx.ink.geometry.internal.threadLocal
 import androidx.ink.nativeloader.NativeLoader
+import androidx.ink.nativeloader.UsedByNative
 
 /**
  * An immutable** complex shape expressed as a set of triangles. This is used to represent the shape
@@ -526,35 +527,23 @@
         NativeLoader.load()
     }
 
-    external fun alloc(): Long // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun alloc(): Long
 
-    external fun free(
-        nativeAddress: Long
-    ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun free(nativeAddress: Long)
 
-    external fun getNativeAddressesOfMeshes(
-        nativeAddress: Long,
-        groupIndex: Int
-    ): LongArray // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
+    external fun getNativeAddressesOfMeshes(nativeAddress: Long, groupIndex: Int): LongArray
 
-    external fun getRenderGroupCount(
-        nativeAddress: Long
-    ): Int // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun getRenderGroupCount(nativeAddress: Long): Int
 
-    external fun getRenderGroupFormat(
-        nativeAddress: Long,
-        groupIndex: Int
-    ): Long // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun getRenderGroupFormat(nativeAddress: Long, groupIndex: Int): Long
 
-    external fun getOutlineCount(
-        nativeAddress: Long,
-        groupIndex: Int
-    ): Int // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun getOutlineCount(nativeAddress: Long, groupIndex: Int): Int
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun getOutlineVertexCount(nativeAddress: Long, groupIndex: Int, outlineIndex: Int): Int
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun fillOutlineMeshIndexAndMeshVertexIndex(
         nativeAddress: Long,
         groupIndex: Int,
@@ -567,7 +556,7 @@
      * JNI method to construct C++ `ModeledShape` and `Triangle` objects and calculate coverage
      * using them.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun modeledShapeTriangleCoverage(
         nativeAddress: Long,
         triangleP0X: Float,
@@ -588,7 +577,7 @@
      * JNI method to construct C++ `ModeledShape` and `Triangle` objects and calculate coverage
      * using them.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun modeledShapeBoxCoverage(
         nativeAddress: Long,
         boxXMin: Float,
@@ -607,7 +596,7 @@
      * JNI method to construct C++ `ModeledShape` and `Quad` objects and calculate coverage using
      * them.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun modeledShapeParallelogramCoverage(
         nativeAddress: Long,
         parallelogramCenterX: Float,
@@ -625,7 +614,7 @@
     ): Float
 
     /** JNI method to construct C++ two `ModeledShape` objects and calculate coverage using them. */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun modeledShapeModeledShapeCoverage(
         thisShapeNativeAddress: Long,
         otherShapeNativeAddress: Long,
@@ -641,7 +630,7 @@
      * JNI method to construct C++ `ModeledShape` and `Triangle` objects and call native
      * `CoverageIsGreaterThan` on them.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun modeledShapeTriangleCoverageIsGreaterThan(
         nativeAddress: Long,
         triangleP0X: Float,
@@ -663,7 +652,7 @@
      * JNI method to construct C++ `ModeledShape` and `Rect` objects and call native
      * `CoverageIsGreaterThan` on them.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun modeledShapeBoxCoverageIsGreaterThan(
         nativeAddress: Long,
         boxXMin: Float,
@@ -683,7 +672,7 @@
      * JNI method to construct C++ `ModeledShape` and `Quad` objects and call native
      * `CoverageIsGreaterThan` on them.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun modeledShapeParallelogramCoverageIsGreaterThan(
         nativeAddress: Long,
         parallelogramCenterX: Float,
@@ -705,7 +694,7 @@
      * JNI method to construct two C++ `ModeledShape` objects and call native
      * `CoverageIsGreaterThan` on them.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun modeledShapeModeledShapeCoverageIsGreaterThan(
         thisShapeNativeAddress: Long,
         otherShapeNativeAddress: Long,
@@ -718,11 +707,7 @@
         otherShapeToThisTransformF: Float,
     ): Boolean
 
-    external fun initializeSpatialIndex(
-        nativeAddress: Long
-    ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun initializeSpatialIndex(nativeAddress: Long)
 
-    external fun isSpatialIndexInitialized(
-        nativeAddress: Long
-    ): Boolean // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun isSpatialIndexInitialized(nativeAddress: Long): Boolean
 }
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Triangle.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Triangle.kt
index abf59f2..52707b2 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Triangle.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/Triangle.kt
@@ -20,6 +20,7 @@
 import androidx.annotation.IntRange
 import androidx.annotation.RestrictTo
 import androidx.ink.nativeloader.NativeLoader
+import androidx.ink.nativeloader.UsedByNative
 
 /**
  * A triangle defined by its three corners [p0], [p1] and [p2]. The order of these points matter - a
@@ -200,7 +201,7 @@
     }
 
     /** Helper method to check if a native `ink::Triangle` contains the native `ink::Point`. */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun nativeContains(
         triangleP0X: Float,
         triangleP0Y: Float,
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/VecNative.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/VecNative.kt
index 3c014a2..7d31117 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/VecNative.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/VecNative.kt
@@ -17,6 +17,7 @@
 package androidx.ink.geometry
 
 import androidx.ink.nativeloader.NativeLoader
+import androidx.ink.nativeloader.UsedByNative
 
 /** Helper functions for Vec. */
 internal object VecNative {
@@ -25,20 +26,16 @@
         NativeLoader.load()
     }
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun unitVec(
         vecX: Float,
         vecY: Float,
         immutableVecClass: Class<ImmutableVec>,
     ): ImmutableVec
 
-    external fun populateUnitVec(
-        vecX: Float,
-        vecY: Float,
-        output: MutableVec
-    ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun populateUnitVec(vecX: Float, vecY: Float, output: MutableVec)
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun absoluteAngleBetween(
         firstVecX: Float,
         firstVecY: Float,
@@ -46,7 +43,7 @@
         secondVecY: Float,
     ): Float
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun signedAngleBetween(
         firstVecX: Float,
         firstVecY: Float,
diff --git a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/internal/testutil/MeshFormatTestHelper.kt b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/internal/testutil/MeshFormatTestHelper.kt
index bfccfe6..5e458a2 100644
--- a/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/internal/testutil/MeshFormatTestHelper.kt
+++ b/ink/ink-geometry/src/jvmAndroidMain/kotlin/androidx/ink/geometry/internal/testutil/MeshFormatTestHelper.kt
@@ -17,6 +17,7 @@
 package androidx.ink.geometry.internal.testutil
 
 import androidx.ink.geometry.MeshFormat
+import androidx.ink.nativeloader.UsedByNative
 
 internal fun BuildTestMeshFormatA(): MeshFormat {
     return MeshFormat(nativeBuildMeshFormatA())
@@ -26,8 +27,6 @@
     return MeshFormat(nativeBuildMeshFormatB())
 }
 
-private external fun nativeBuildMeshFormatA():
-    Long // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+@UsedByNative private external fun nativeBuildMeshFormatA(): Long
 
-private external fun nativeBuildMeshFormatB():
-    Long // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+@UsedByNative private external fun nativeBuildMeshFormatB(): Long
diff --git a/ink/ink-nativeloader/build.gradle b/ink/ink-nativeloader/build.gradle
index 103dc2d..4ebf7c3 100644
--- a/ink/ink-nativeloader/build.gradle
+++ b/ink/ink-nativeloader/build.gradle
@@ -65,4 +65,5 @@
     type = LibraryType.PUBLISHED_LIBRARY
     inceptionYear = "2024"
     description = "Native dependencies for Ink"
+    metalavaK2UastEnabled = false
 }
diff --git a/ink/ink-nativeloader/external_referenced_by_native.pgcfg b/ink/ink-nativeloader/external_referenced_by_native.pgcfg
new file mode 100644
index 0000000..0eac730
--- /dev/null
+++ b/ink/ink-nativeloader/external_referenced_by_native.pgcfg
@@ -0,0 +1,46 @@
+# Proguard configuration to retain stuff outside of Ink that is called
+# from JNI code via reflection (e.g. Get[Static]MethodID (including in method
+# signatures), Get[Static]FieldID, FindClass). Stuff in Ink that's
+# called this way should be annotated with Ink's @UsedByNative instead.
+# (This includes methods referenced in GetMethodID and classes in those methods'
+# signatures.)
+
+# All the classes referenced in calls to ink::jni::ThrowException. These need
+# to be preserved from pruning as well as renaming, since the objects are
+# originating on the C++ side without necessarily having a reference to the
+# class on the JVM side (though it would be extremely surprising if any
+# of these actually got pruned).
+-if class androidx.ink.nativeloader.NativeLoader
+-keep class java.lang.IllegalArgumentException {
+    <init>(java.lang.String);
+}
+
+-if class androidx.ink.nativeloader.NativeLoader
+-keep class java.lang.IllegalStateException {
+    <init>(java.lang.String);
+}
+
+-if class androidx.ink.nativeloader.NativeLoader
+-keep class java.lang.IndexOutOfBoundsException {
+    <init>(java.lang.String);
+}
+
+-if class androidx.ink.nativeloader.NativeLoader
+-keep class java.lang.NoSuchElementException {
+    <init>(java.lang.String);
+}
+
+-if class androidx.ink.nativeloader.NativeLoader
+-keep class java.lang.NullPointerException {
+    <init>(java.lang.String);
+}
+
+-if class androidx.ink.nativeloader.NativeLoader
+-keep class java.lang.OutOfMemoryError {
+    <init>(java.lang.String);
+}
+
+-if class androidx.ink.nativeloader.NativeLoader
+-keep class java.lang.UnsupportedOperationException {
+    <init>(java.lang.String);
+}
diff --git a/ink/ink-nativeloader/src/commonMain/kotlin/androidx/ink/nativeloader/UsedByNative.kt b/ink/ink-nativeloader/src/commonMain/kotlin/androidx/ink/nativeloader/UsedByNative.kt
new file mode 100644
index 0000000..7531e6b
--- /dev/null
+++ b/ink/ink-nativeloader/src/commonMain/kotlin/androidx/ink/nativeloader/UsedByNative.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.ink.nativeloader
+
+import androidx.annotation.RestrictTo
+import kotlin.annotation.AnnotationTarget
+import kotlin.annotation.Target
+
+/**
+ * Use this to annotate methods, fields, and types that are referenced by name from native code to
+ * prevent them from being removed as unused.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@MustBeDocumented
+@Target(
+    AnnotationTarget.FUNCTION,
+    AnnotationTarget.FIELD,
+    AnnotationTarget.CONSTRUCTOR,
+    AnnotationTarget.CLASS,
+    AnnotationTarget.PROPERTY_GETTER,
+    AnnotationTarget.PROPERTY_SETTER,
+)
+public annotation class UsedByNative
diff --git a/ink/ink-nativeloader/used_by_native.pgcfg b/ink/ink-nativeloader/used_by_native.pgcfg
new file mode 100644
index 0000000..214493d
--- /dev/null
+++ b/ink/ink-nativeloader/used_by_native.pgcfg
@@ -0,0 +1,23 @@
+# Keep the attributes that contain annotations.
+-keepattributes RuntimeVisible*Annotation*
+
+# @UsedByNative should be used to annotate things referenced from name by JNI.
+# This includes external methods in the Kotlin code and classes whose type
+# is referenced by name in JNI C++ code, as well as any method that is looked
+# up by name.
+-if class androidx.ink.nativeloader.NativeLoader
+-keep class androidx.ink.nativeloader.UsedByNative
+
+# Keep annotated class names.
+-if class androidx.ink.nativeloader.NativeLoader
+-keepnames @androidx.ink.nativeloader.UsedByNative class * {
+  <init>();
+}
+
+# Keep annotated class members if the class is kept. This is preserved not only
+# from renaming but also from pruning, since some of the annotated methods may
+# only be used as callbacks from native code.
+-if class androidx.ink.nativeloader.NativeLoader
+-keepclassmembers class * {
+    @androidx.ink.nativeloader.UsedByNative *;
+}
diff --git a/ink/ink-rendering/api/current.txt b/ink/ink-rendering/api/current.txt
index adc06c7..9635393 100644
--- a/ink/ink-rendering/api/current.txt
+++ b/ink/ink-rendering/api/current.txt
@@ -3,6 +3,11 @@
 
   public interface CanvasStrokeRenderer {
     method public static androidx.ink.rendering.android.canvas.CanvasStrokeRenderer create();
+    method public void draw(android.graphics.Canvas canvas, androidx.ink.strokes.InProgressStroke inProgressStroke, android.graphics.Matrix strokeToScreenTransform);
+    method public void draw(android.graphics.Canvas canvas, androidx.ink.strokes.InProgressStroke inProgressStroke, androidx.ink.geometry.AffineTransform strokeToScreenTransform);
+    method public void draw(android.graphics.Canvas canvas, androidx.ink.strokes.Stroke stroke, android.graphics.Matrix strokeToScreenTransform);
+    method public void draw(android.graphics.Canvas canvas, androidx.ink.strokes.Stroke stroke, androidx.ink.geometry.AffineTransform strokeToScreenTransform);
+    method @Px public default int strokeModifiedRegionOutsetPx();
     field public static final androidx.ink.rendering.android.canvas.CanvasStrokeRenderer.Companion Companion;
   }
 
diff --git a/ink/ink-rendering/api/restricted_current.txt b/ink/ink-rendering/api/restricted_current.txt
index e06360d..fa05dd5 100644
--- a/ink/ink-rendering/api/restricted_current.txt
+++ b/ink/ink-rendering/api/restricted_current.txt
@@ -3,6 +3,11 @@
 
   public interface CanvasStrokeRenderer {
     method public static androidx.ink.rendering.android.canvas.CanvasStrokeRenderer create();
+    method public void draw(android.graphics.Canvas canvas, androidx.ink.strokes.InProgressStroke inProgressStroke, android.graphics.Matrix strokeToScreenTransform);
+    method public void draw(android.graphics.Canvas canvas, androidx.ink.strokes.InProgressStroke inProgressStroke, androidx.ink.geometry.AffineTransform strokeToScreenTransform);
+    method public void draw(android.graphics.Canvas canvas, androidx.ink.strokes.Stroke stroke, android.graphics.Matrix strokeToScreenTransform);
+    method public void draw(android.graphics.Canvas canvas, androidx.ink.strokes.Stroke stroke, androidx.ink.geometry.AffineTransform strokeToScreenTransform);
+    method @Px public default int strokeModifiedRegionOutsetPx();
     field public static final androidx.ink.rendering.android.canvas.CanvasStrokeRenderer.Companion Companion;
   }
 
diff --git a/ink/ink-rendering/build.gradle b/ink/ink-rendering/build.gradle
index e7df440..247a714 100644
--- a/ink/ink-rendering/build.gradle
+++ b/ink/ink-rendering/build.gradle
@@ -48,6 +48,7 @@
         implementation(libs.espressoCore)
         implementation(libs.junit)
         implementation(libs.truth)
+        implementation(project(":core:core-ktx"))
 	implementation(project(":test:screenshot:screenshot"))
       }
     }
diff --git a/ink/ink-rendering/src/androidInstrumentedTest/kotlin/androidx/ink/rendering/android/canvas/CanvasStrokeRendererTestActivity.kt b/ink/ink-rendering/src/androidInstrumentedTest/kotlin/androidx/ink/rendering/android/canvas/CanvasStrokeRendererTestActivity.kt
index 60ac4fa..f2d93ee 100644
--- a/ink/ink-rendering/src/androidInstrumentedTest/kotlin/androidx/ink/rendering/android/canvas/CanvasStrokeRendererTestActivity.kt
+++ b/ink/ink-rendering/src/androidInstrumentedTest/kotlin/androidx/ink/rendering/android/canvas/CanvasStrokeRendererTestActivity.kt
@@ -27,6 +27,8 @@
 import android.view.View
 import android.widget.GridLayout
 import android.widget.TextView
+import androidx.core.graphics.withMatrix
+import androidx.core.graphics.withTranslation
 import androidx.ink.brush.ExperimentalInkCustomBrushApi
 import androidx.ink.geometry.AffineTransform
 import androidx.ink.geometry.BoxAccumulator
@@ -143,33 +145,27 @@
 
             // Draw Stroke next to InProgressStroke, with a small gap between them.
             val stroke = inProgressStroke.toImmutable()
-            renderer.draw(
-                canvas,
-                stroke,
-                ImmutableAffineTransform.translate(ImmutableVec(finishedStrokeTranslateX, 0f)),
-            )
+            canvas.withTranslation(finishedStrokeTranslateX) {
+                renderer.draw(
+                    canvas,
+                    stroke,
+                    ImmutableAffineTransform.translate(ImmutableVec(finishedStrokeTranslateX, 0f)),
+                )
+            }
 
             // Draw the InProgressStroke and Stroke again in a second row with a non-trivial
             // transform
             // and using android.graphics.Matrix instead of AffineTransform.
-            renderer.draw(
-                canvas,
-                inProgressStroke,
+            val transform =
                 Matrix().apply {
                     setSkew(0.5f, 0f)
                     postScale(1f, scaleValueY)
                     postTranslate(0f, scaledStrokeTranslateY)
-                },
-            )
-            renderer.draw(
-                canvas,
-                stroke,
-                Matrix().apply {
-                    setSkew(0.5f, 0f)
-                    postScale(1f, scaleValueY)
-                    postTranslate(finishedStrokeTranslateX, scaledStrokeTranslateY)
-                },
-            )
+                }
+            canvas.withMatrix(transform) { renderer.draw(canvas, inProgressStroke, transform) }
+
+            transform.postTranslate(finishedStrokeTranslateX, 0f)
+            canvas.withMatrix(transform) { renderer.draw(canvas, stroke, transform) }
         }
     }
 
diff --git a/ink/ink-rendering/src/androidInstrumentedTest/kotlin/androidx/ink/rendering/android/canvas/internal/CanvasMeshRendererScreenshotTestActivity.kt b/ink/ink-rendering/src/androidInstrumentedTest/kotlin/androidx/ink/rendering/android/canvas/internal/CanvasMeshRendererScreenshotTestActivity.kt
index a5ef162..3592f4b 100644
--- a/ink/ink-rendering/src/androidInstrumentedTest/kotlin/androidx/ink/rendering/android/canvas/internal/CanvasMeshRendererScreenshotTestActivity.kt
+++ b/ink/ink-rendering/src/androidInstrumentedTest/kotlin/androidx/ink/rendering/android/canvas/internal/CanvasMeshRendererScreenshotTestActivity.kt
@@ -25,6 +25,7 @@
 import android.os.Bundle
 import android.view.View
 import androidx.annotation.RequiresApi
+import androidx.core.graphics.withMatrix
 import androidx.ink.brush.Brush
 import androidx.ink.brush.ExperimentalInkCustomBrushApi
 import androidx.ink.brush.InputToolType
@@ -89,10 +90,12 @@
 
             // The empty stroke should of course not be visible, but the [draw] call should succeed.
             renderer.draw(canvas, emptyStroke, Matrix.IDENTITY_MATRIX)
+
             // Expected result: pink stroke on left, large green rotated stroke on right.
-            renderer.draw(canvas, stroke, transform)
+            canvas.withMatrix(transform) { renderer.draw(canvas, stroke, transform) }
+
             canvas.translate(xBetweenStrokes, 0F)
-            renderer.draw(canvas, stroke2, transform2)
+            canvas.withMatrix(transform2) { renderer.draw(canvas, stroke2, transform2) }
         }
     }
 }
diff --git a/ink/ink-rendering/src/androidMain/kotlin/androidx/ink/rendering/android/canvas/CanvasStrokeRenderer.kt b/ink/ink-rendering/src/androidMain/kotlin/androidx/ink/rendering/android/canvas/CanvasStrokeRenderer.kt
index 29b6647..bcb4e99 100644
--- a/ink/ink-rendering/src/androidMain/kotlin/androidx/ink/rendering/android/canvas/CanvasStrokeRenderer.kt
+++ b/ink/ink-rendering/src/androidMain/kotlin/androidx/ink/rendering/android/canvas/CanvasStrokeRenderer.kt
@@ -32,6 +32,39 @@
 /**
  * Renders strokes to a [Canvas].
  *
+ * Instead of calling the [draw] methods here directly, it may be simpler to pass an instance of
+ * [CanvasStrokeRenderer] to [androidx.ink.rendering.android.view.ViewStrokeRenderer] and use it to
+ * calculate transform matrix values.
+ *
+ * An example of how to use [CanvasStrokeRenderer.draw] directly:
+ * ```
+ * class MyView {
+ *   // Update these according to app business logic, and call `MyView.invalidate()`
+ *   val worldToViewTransform = Matrix() // Call e.g. `setScale(2F)` to zoom in 2x
+ *   val strokesWithTransforms = mutableMapOf<Stroke, Matrix>()
+ *
+ *   private val strokeToViewTransform = Matrix() // reusable scratch object
+ *   private val renderer = CanvasStrokeRenderer.create()
+ *
+ *   fun onDraw(canvas: Canvas) {
+ *     for ((stroke, strokeToWorldTransform) in strokesWithTransforms) {
+ *       // Combine worldToViewTransform (drawing surface being panned/zoomed/rotated) with
+ *       // strokeToWorldTransform (stroke itself being moved/scaled/rotated within the drawing
+ *       // surface) to get the overall transform of this stroke.
+ *       strokeToViewTransform.set(strokeToWorldTransform)
+ *       strokeToViewTransform.postConcat(worldToViewTransform)
+ *
+ *       canvas.withMatrix(strokeToViewTransform) {
+ *         // If coordinates of MyView are scaled/rotated from screen coordinates, then those
+ *         // scale/rotation values should be multiplied into the strokeToScreenTransform
+ *         // argument to renderer.draw.
+ *         renderer.draw(canvas, stroke, strokeToViewTransform)
+ *       }
+ *     }
+ *   }
+ * }
+ * ```
+ *
  * In almost all cases, a developer should use an implementation of this interface obtained from
  * [CanvasStrokeRenderer.create].
  *
@@ -52,63 +85,63 @@
 public interface CanvasStrokeRenderer {
 
     /**
-     * Render a single [stroke] on the provided [canvas], with its positions transformed by
-     * [strokeToCanvasTransform].
+     * Render a single [stroke] on the provided [canvas].
      *
-     * To avoid needing to calculate and maintain [strokeToCanvasTransform], consider using
+     * To avoid needing to calculate and maintain [strokeToScreenTransform], consider using
      * [androidx.ink.rendering.android.view.ViewStrokeRenderer] instead.
      *
-     * The [strokeToCanvasTransform] should represent the complete transformation from stroke
-     * coordinates to the canvas, modulo translation. Any existing transforms applied to [canvas]
-     * should be undone prior to calling [draw].
+     * The [strokeToScreenTransform] should represent the complete transformation from stroke
+     * coordinates to the screen, modulo translation. This transform will not be applied to the
+     * [canvas] in any way, as it may be made up of several individual transforms applied to the
+     * [canvas] during an app’s drawing logic. If this transform is inaccurate, strokes may appear
+     * blurry or aliased.
      */
     // TODO: b/353561141 - Reference ComposeStrokeRenderer above once implemented.
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-    public fun draw(canvas: Canvas, stroke: Stroke, strokeToCanvasTransform: AffineTransform)
+    public fun draw(canvas: Canvas, stroke: Stroke, strokeToScreenTransform: AffineTransform)
 
     /**
-     * Render a single [stroke] on the provided [canvas], with its positions transformed by
-     * [strokeToCanvasTransform].
+     * Render a single [stroke] on the provided [canvas].
      *
-     * To avoid needing to calculate and maintain [strokeToCanvasTransform], consider using
+     * To avoid needing to calculate and maintain [strokeToScreenTransform], consider using
      * [androidx.ink.rendering.android.view.ViewStrokeRenderer] instead.
      *
-     * The [strokeToCanvasTransform] must be affine. It should represent the complete transformation
-     * from stroke coordinates to the canvas, modulo translation. Any existing transforms applied to
-     * [canvas] should be undone prior to calling [draw].
+     * The [strokeToScreenTransform] must be affine. It should represent the complete transformation
+     * from stroke coordinates to the canvas, modulo translation. This transform will not be applied
+     * to the [canvas] in any way, as it may be made up of several individual transforms applied to
+     * the [canvas] during an app’s drawing logic. If this transform is inaccurate, strokes may
+     * appear blurry or aliased.
      */
     // TODO: b/353561141 - Reference ComposeStrokeRenderer above once implemented.
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-    public fun draw(canvas: Canvas, stroke: Stroke, strokeToCanvasTransform: Matrix)
+    public fun draw(canvas: Canvas, stroke: Stroke, strokeToScreenTransform: Matrix)
 
     /**
-     * Render a single [inProgressStroke] on the provided [canvas], with its positions transformed
-     * by [strokeToCanvasTransform].
+     * Render a single [inProgressStroke] on the provided [canvas].
      *
-     * The [strokeToCanvasTransform] should represent the complete transformation from stroke
-     * coordinates to the canvas, modulo translation. Any existing transforms applied to [canvas]
-     * should be undone prior to calling [draw].
+     * The [strokeToScreenTransform] should represent the complete transformation from stroke
+     * coordinates to the canvas, modulo translation. This transform will not be applied to the
+     * [canvas] in any way, as it may be made up of several individual transforms applied to the
+     * [canvas] during an app’s drawing logic. If this transform is inaccurate, strokes may appear
+     * blurry or aliased.
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
     public fun draw(
         canvas: Canvas,
         inProgressStroke: InProgressStroke,
-        strokeToCanvasTransform: AffineTransform,
+        strokeToScreenTransform: AffineTransform,
     )
 
     /**
-     * Render a single [inProgressStroke] on the provided [canvas], with its positions transformed
-     * by [strokeToCanvasTransform].
+     * Render a single [inProgressStroke] on the provided [canvas].
      *
-     * The [strokeToCanvasTransform] must be affine. It should represent the complete transformation
-     * from stroke coordinates to the canvas, modulo translation. Any existing transforms applied to
-     * [canvas] should be undone prior to calling [draw].
+     * The [strokeToScreenTransform] must be affine. It should represent the complete transformation
+     * from stroke coordinates to the canvas, modulo translation. This transform will not be applied
+     * to the [canvas] in any way, as it may be made up of several individual transforms applied to
+     * the [canvas] during an app’s drawing logic. If this transform is inaccurate, strokes may
+     * appear blurry or aliased.
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
     public fun draw(
         canvas: Canvas,
         inProgressStroke: InProgressStroke,
-        strokeToCanvasTransform: Matrix,
+        strokeToScreenTransform: Matrix,
     )
 
     /**
@@ -121,9 +154,7 @@
      * lowest value that avoids the artifacts, as larger values will be less performant, and effects
      * that rely on larger values will be less compatible with stroke geometry operations.
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // PublicApiNotReadyForJetpackReview
-    @Px
-    public fun strokeModifiedRegionOutsetPx(): Int = 3
+    @Px public fun strokeModifiedRegionOutsetPx(): Int = 3
 
     public companion object {
 
diff --git a/ink/ink-rendering/src/androidMain/kotlin/androidx/ink/rendering/android/canvas/StrokeDrawScope.kt b/ink/ink-rendering/src/androidMain/kotlin/androidx/ink/rendering/android/canvas/StrokeDrawScope.kt
index a4d1a05..f287e8b 100644
--- a/ink/ink-rendering/src/androidMain/kotlin/androidx/ink/rendering/android/canvas/StrokeDrawScope.kt
+++ b/ink/ink-rendering/src/androidMain/kotlin/androidx/ink/rendering/android/canvas/StrokeDrawScope.kt
@@ -76,14 +76,6 @@
      */
     private val localToScreenTransform = Matrix()
 
-    /**
-     * Pre-allocated inverse of [localToScreenTransform] calculated once per call to [drawStroke].
-     *
-     * TODO: b/353302113 - Delete once the renderer can draw without modifying canvas transform
-     *   state.
-     */
-    private val screenToLocalTransform = Matrix()
-
     /** Overwrite this object for reuse. */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public fun onDrawStart(canvasToScreenTransform: Matrix, newCanvas: Canvas) {
@@ -107,16 +99,6 @@
             // (Local -> Screen) = (Initial -> Screen) * (Local -> Initial)
             postConcat(initialCanvasToScreenTransform)
         }
-
-        // Second, apply the inverse of (Local -> Screen) to the [Canvas], since the renderer will
-        // apply the provided transform to the [Canvas]. This cancels the two out.
-        // TODO: b/353302113 - Do not modify Canvas transform when new draw API is available.
-        canvas.save()
-        localToScreenTransform.invert(screenToLocalTransform)
-        canvas.concat(screenToLocalTransform)
-
         renderer.draw(canvas, stroke, localToScreenTransform)
-
-        canvas.restore()
     }
 }
diff --git a/ink/ink-rendering/src/androidMain/kotlin/androidx/ink/rendering/android/canvas/internal/CanvasMeshRenderer.kt b/ink/ink-rendering/src/androidMain/kotlin/androidx/ink/rendering/android/canvas/internal/CanvasMeshRenderer.kt
index ebd2194..146e7465 100644
--- a/ink/ink-rendering/src/androidMain/kotlin/androidx/ink/rendering/android/canvas/internal/CanvasMeshRenderer.kt
+++ b/ink/ink-rendering/src/androidMain/kotlin/androidx/ink/rendering/android/canvas/internal/CanvasMeshRenderer.kt
@@ -43,6 +43,7 @@
 import androidx.ink.geometry.MeshFormat
 import androidx.ink.geometry.populateMatrix
 import androidx.ink.nativeloader.NativeLoader
+import androidx.ink.nativeloader.UsedByNative
 import androidx.ink.rendering.android.TextureBitmapStore
 import androidx.ink.rendering.android.canvas.CanvasStrokeRenderer
 import androidx.ink.strokes.InProgressStroke
@@ -165,8 +166,8 @@
     private val scratchFirstInput = StrokeInput()
     private val scratchLastInput = StrokeInput()
 
-    override fun draw(canvas: Canvas, stroke: Stroke, strokeToCanvasTransform: AffineTransform) {
-        strokeToCanvasTransform.populateMatrix(scratchMatrix)
+    override fun draw(canvas: Canvas, stroke: Stroke, strokeToScreenTransform: AffineTransform) {
+        strokeToScreenTransform.populateMatrix(scratchMatrix)
         draw(canvas, stroke, scratchMatrix)
     }
 
@@ -175,21 +176,19 @@
      *
      * @param canvas The [Canvas] to draw to.
      * @param stroke The [Stroke] to draw.
-     * @param strokeToCanvasTransform The transform [Matrix] to convert from [Stroke] actual
-     *   coordinates to the coordinates of [canvas]. It is important to pass this here to be applied
-     *   internally rather than applying it to [canvas] in calling code, to ensure anti-aliasing has
-     *   the information it needs to render properly. In addition, any transforms previously applied
-     *   to [canvas] must only be translations, or rotations in multiples of 90 degrees. If you are
-     *   not transforming [canvas] yourself then this will be correct, as the [android.view.View]
-     *   hierarchy applies only translations by default. If you are rendering in a
+     * @param strokeToScreenTransform The transform [Matrix] to convert from [Stroke] coordinates to
+     *   the coordinates of pixels on the screen used to display the stroke. It is important to pass
+     *   this here to be applied internally rather than applying it to [canvas] in calling code, to
+     *   ensure anti-aliasing has the information it needs to render properly. Also, any additional
+     *   transforms applied to [canvas] must only be translations. If you are rendering in a
      *   [android.view.View] where it (or one of its ancestors) is rotated or scaled within its
      *   parent, or if you are applying rotation or scaling transforms to [canvas] yourself, then
-     *   care must be taken to undo those transforms before calling this method, and calling this
-     *   method with a full stroke-to-screen (modulo translation or multi-90 degree rotation)
-     *   transform. Without this, anti-aliasing at the edge of strokes will not render properly.
+     *   care must be taken so that these transformations are reflected in the
+     *   [strokeToScreenTransform]. Without this, anti-aliasing at the edge of strokes will not
+     *   render properly.
      */
-    override fun draw(canvas: Canvas, stroke: Stroke, strokeToCanvasTransform: Matrix) {
-        require(strokeToCanvasTransform.isAffine) { "strokeToCanvasTransform must be affine" }
+    override fun draw(canvas: Canvas, stroke: Stroke, strokeToScreenTransform: Matrix) {
+        require(strokeToScreenTransform.isAffine) { "strokeToScreenTransform must be affine" }
         if (stroke.inputs.isEmpty()) return // nothing to draw
         stroke.inputs.populate(0, scratchFirstInput)
         stroke.inputs.populate(stroke.inputs.size - 1, scratchLastInput)
@@ -212,7 +211,7 @@
                 drawFromStroke(
                     canvas,
                     mesh,
-                    strokeToCanvasTransform,
+                    strokeToScreenTransform,
                     stroke.brush.composeColor,
                     blendMode,
                     androidPaint,
@@ -275,16 +274,7 @@
                 cachedMeshData.androidMesh
             }
 
-        canvas.save()
-        try {
-            canvas.concat(meshToCanvasTransform)
-            canvas.drawMesh(androidMesh, blendMode, paint)
-        } finally {
-            // If any exceptions occur while drawing, restore the canvas so that restore is always
-            // called
-            // after canvas.save().
-            canvas.restore()
-        }
+        canvas.drawMesh(androidMesh, blendMode, paint)
 
         if (!uniformBugFixed) {
             val currentTimeMillis = getDurationTimeMillis()
@@ -307,63 +297,54 @@
     override fun draw(
         canvas: Canvas,
         inProgressStroke: InProgressStroke,
-        strokeToCanvasTransform: AffineTransform,
+        strokeToScreenTransform: AffineTransform,
     ) {
-        strokeToCanvasTransform.populateMatrix(scratchMatrix)
+        strokeToScreenTransform.populateMatrix(scratchMatrix)
         draw(canvas, inProgressStroke, scratchMatrix)
     }
 
     override fun draw(
         canvas: Canvas,
         inProgressStroke: InProgressStroke,
-        strokeToCanvasTransform: Matrix,
+        strokeToScreenTransform: Matrix,
     ) {
         val brush =
             checkNotNull(inProgressStroke.brush) {
                 "Attempting to draw an InProgressStroke that has not been started."
             }
-        require(strokeToCanvasTransform.isAffine) { "strokeToCanvasTransform must be affine" }
+        require(strokeToScreenTransform.isAffine) { "strokeToScreenTransform must be affine" }
         val inputCount = inProgressStroke.getInputCount()
         if (inputCount == 0) return // nothing to draw
         inProgressStroke.populateInput(scratchFirstInput, 0)
         inProgressStroke.populateInput(scratchLastInput, inputCount - 1)
         fillObjectToCanvasLinearComponent(
-            strokeToCanvasTransform,
+            strokeToScreenTransform,
             objectToCanvasLinearComponentScratch
         )
         val brushCoatCount = inProgressStroke.getBrushCoatCount()
-        canvas.save()
-        try {
-            canvas.concat(strokeToCanvasTransform)
-            for (coatIndex in 0 until brushCoatCount) {
-                val brushPaint = brush.family.coats[coatIndex].paint
-                val blendMode = finalBlendMode(brushPaint)
-                val androidPaint =
-                    paintCache.obtain(
-                        brushPaint,
-                        AndroidColor.WHITE,
-                        brush.size,
-                        scratchFirstInput,
-                        scratchLastInput,
-                    )
-                val inProgressMeshData = obtainInProgressMeshData(inProgressStroke, coatIndex)
-                for (meshIndex in 0 until inProgressMeshData.androidMeshes.size) {
-                    val androidMesh = inProgressMeshData.androidMeshes[meshIndex] ?: continue
-                    updateAndroidMesh(
-                        androidMesh,
-                        inProgressStroke.getMeshFormat(coatIndex, meshIndex),
-                        objectToCanvasLinearComponentScratch,
-                        brush.composeColor,
-                        attributeUnpackingParams = null,
-                    )
-                    canvas.drawMesh(androidMesh, blendMode, androidPaint)
-                }
+        for (coatIndex in 0 until brushCoatCount) {
+            val brushPaint = brush.family.coats[coatIndex].paint
+            val blendMode = finalBlendMode(brushPaint)
+            val androidPaint =
+                paintCache.obtain(
+                    brushPaint,
+                    AndroidColor.WHITE,
+                    brush.size,
+                    scratchFirstInput,
+                    scratchLastInput,
+                )
+            val inProgressMeshData = obtainInProgressMeshData(inProgressStroke, coatIndex)
+            for (meshIndex in 0 until inProgressMeshData.androidMeshes.size) {
+                val androidMesh = inProgressMeshData.androidMeshes[meshIndex] ?: continue
+                updateAndroidMesh(
+                    androidMesh,
+                    inProgressStroke.getMeshFormat(coatIndex, meshIndex),
+                    objectToCanvasLinearComponentScratch,
+                    brush.composeColor,
+                    attributeUnpackingParams = null,
+                )
+                canvas.drawMesh(androidMesh, blendMode, androidPaint)
             }
-        } finally {
-            // If any exceptions occur while drawing, restore the canvas so that restore is always
-            // called
-            // after canvas.save().
-            canvas.restore()
         }
     }
 
@@ -726,7 +707,7 @@
      * @throws IllegalArgumentException If an unrecognized format was passed in, i.e. when
      *   [nativeIsMeshFormatRenderable] returns false.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun fillSkiaMeshSpecData(
         meshFormatNativeAddress: Long,
         isPacked: Boolean,
@@ -753,7 +734,7 @@
      *
      * [fillSkiaMeshSpecData] throws IllegalArgumentException when this method returns false.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeIsMeshFormatRenderable(
         meshFormatNativeAddress: Long,
         isPacked: Boolean,
diff --git a/ink/ink-rendering/src/androidMain/kotlin/androidx/ink/rendering/android/canvas/internal/CanvasPathRenderer.kt b/ink/ink-rendering/src/androidMain/kotlin/androidx/ink/rendering/android/canvas/internal/CanvasPathRenderer.kt
index 7f185a0..39f7ee6 100644
--- a/ink/ink-rendering/src/androidMain/kotlin/androidx/ink/rendering/android/canvas/internal/CanvasPathRenderer.kt
+++ b/ink/ink-rendering/src/androidMain/kotlin/androidx/ink/rendering/android/canvas/internal/CanvasPathRenderer.kt
@@ -28,7 +28,6 @@
 import androidx.ink.geometry.AffineTransform
 import androidx.ink.geometry.MutableVec
 import androidx.ink.geometry.PartitionedMesh
-import androidx.ink.geometry.populateMatrix
 import androidx.ink.rendering.android.TextureBitmapStore
 import androidx.ink.rendering.android.canvas.CanvasStrokeRenderer
 import androidx.ink.strokes.InProgressStroke
@@ -95,24 +94,24 @@
         @FloatRange(from = 0.0) brushSize: Float,
         firstInput: StrokeInput,
         lastInput: StrokeInput,
-        strokeToCanvasTransform: Matrix,
     ) {
         val paint = paintCache.obtain(brushPaint, color.toArgb(), brushSize, firstInput, lastInput)
-        canvas.save()
-        try {
-            canvas.concat(strokeToCanvasTransform)
-            canvas.drawPath(path, paint)
-        } finally {
-            canvas.restore()
-        }
+        canvas.drawPath(path, paint)
     }
 
-    override fun draw(canvas: Canvas, stroke: Stroke, strokeToCanvasTransform: AffineTransform) {
-        strokeToCanvasTransform.populateMatrix(scratchMatrix)
+    override fun draw(
+        canvas: Canvas,
+        stroke: Stroke,
+        @Suppress("UNUSED_PARAMETER") strokeToScreenTransform: AffineTransform,
+    ) {
         draw(canvas, stroke, scratchMatrix)
     }
 
-    override fun draw(canvas: Canvas, stroke: Stroke, strokeToCanvasTransform: Matrix) {
+    override fun draw(
+        canvas: Canvas,
+        stroke: Stroke,
+        @Suppress("UNUSED_PARAMETER") strokeToScreenTransform: Matrix,
+    ) {
         if (stroke.inputs.isEmpty()) return // nothing to draw
         stroke.inputs.populate(0, scratchFirstInput)
         stroke.inputs.populate(stroke.inputs.size - 1, scratchLastInput)
@@ -125,7 +124,6 @@
                 stroke.brush.size,
                 scratchFirstInput,
                 scratchLastInput,
-                strokeToCanvasTransform,
             )
         }
     }
@@ -133,16 +131,15 @@
     override fun draw(
         canvas: Canvas,
         inProgressStroke: InProgressStroke,
-        strokeToCanvasTransform: AffineTransform,
+        @Suppress("UNUSED_PARAMETER") strokeToScreenTransform: AffineTransform,
     ) {
-        strokeToCanvasTransform.populateMatrix(scratchMatrix)
         draw(canvas, inProgressStroke, scratchMatrix)
     }
 
     override fun draw(
         canvas: Canvas,
         inProgressStroke: InProgressStroke,
-        strokeToCanvasTransform: Matrix,
+        @Suppress("UNUSED_PARAMETER") strokeToScreenTransform: Matrix,
     ) {
         val brush =
             checkNotNull(inProgressStroke.brush) {
@@ -161,7 +158,6 @@
                 brush.size,
                 scratchFirstInput,
                 scratchLastInput,
-                strokeToCanvasTransform,
             )
         }
     }
diff --git a/ink/ink-rendering/src/androidMain/kotlin/androidx/ink/rendering/android/canvas/internal/CanvasStrokeUnifiedRenderer.kt b/ink/ink-rendering/src/androidMain/kotlin/androidx/ink/rendering/android/canvas/internal/CanvasStrokeUnifiedRenderer.kt
index ce04f14..51b3719 100644
--- a/ink/ink-rendering/src/androidMain/kotlin/androidx/ink/rendering/android/canvas/internal/CanvasStrokeUnifiedRenderer.kt
+++ b/ink/ink-rendering/src/androidMain/kotlin/androidx/ink/rendering/android/canvas/internal/CanvasStrokeUnifiedRenderer.kt
@@ -59,29 +59,29 @@
         throw IllegalArgumentException("Cannot draw $stroke")
     }
 
-    override fun draw(canvas: Canvas, stroke: Stroke, strokeToCanvasTransform: AffineTransform) {
-        getDelegateRendererOrThrow(stroke).draw(canvas, stroke, strokeToCanvasTransform)
+    override fun draw(canvas: Canvas, stroke: Stroke, strokeToScreenTransform: AffineTransform) {
+        getDelegateRendererOrThrow(stroke).draw(canvas, stroke, strokeToScreenTransform)
     }
 
-    override fun draw(canvas: Canvas, stroke: Stroke, strokeToCanvasTransform: Matrix) {
-        getDelegateRendererOrThrow(stroke).draw(canvas, stroke, strokeToCanvasTransform)
+    override fun draw(canvas: Canvas, stroke: Stroke, strokeToScreenTransform: Matrix) {
+        getDelegateRendererOrThrow(stroke).draw(canvas, stroke, strokeToScreenTransform)
     }
 
     override fun draw(
         canvas: Canvas,
         inProgressStroke: InProgressStroke,
-        strokeToCanvasTransform: AffineTransform,
+        strokeToScreenTransform: AffineTransform,
     ) {
         val delegateRenderer = meshRenderer ?: pathRenderer
-        delegateRenderer.draw(canvas, inProgressStroke, strokeToCanvasTransform)
+        delegateRenderer.draw(canvas, inProgressStroke, strokeToScreenTransform)
     }
 
     override fun draw(
         canvas: Canvas,
         inProgressStroke: InProgressStroke,
-        strokeToCanvasTransform: Matrix,
+        strokeToScreenTransform: Matrix,
     ) {
         val delegateRenderer = meshRenderer ?: pathRenderer
-        delegateRenderer.draw(canvas, inProgressStroke, strokeToCanvasTransform)
+        delegateRenderer.draw(canvas, inProgressStroke, strokeToScreenTransform)
     }
 }
diff --git a/ink/ink-strokes/src/jvmAndroidMain/kotlin/androidx/ink/strokes/InProgressStroke.kt b/ink/ink-strokes/src/jvmAndroidMain/kotlin/androidx/ink/strokes/InProgressStroke.kt
index 7af48ff..2a26901 100644
--- a/ink/ink-strokes/src/jvmAndroidMain/kotlin/androidx/ink/strokes/InProgressStroke.kt
+++ b/ink/ink-strokes/src/jvmAndroidMain/kotlin/androidx/ink/strokes/InProgressStroke.kt
@@ -24,6 +24,7 @@
 import androidx.ink.geometry.MeshFormat
 import androidx.ink.geometry.MutableVec
 import androidx.ink.nativeloader.NativeLoader
+import androidx.ink.nativeloader.UsedByNative
 import java.nio.ByteBuffer
 import java.nio.ByteOrder
 import java.nio.ShortBuffer
@@ -466,20 +467,14 @@
     }
 
     /** Create underlying native object and return reference for all subsequent native calls. */
-    private external fun nativeCreateInProgressStroke():
-        Long // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun nativeCreateInProgressStroke(): Long
 
-    private external fun nativeClear(
-        nativePointer: Long
-    ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun nativeClear(nativePointer: Long)
 
-    private external fun nativeStart(
-        nativePointer: Long,
-        brushNativePointer: Long
-    ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun nativeStart(nativePointer: Long, brushNativePointer: Long)
 
     /** Returns null on success or an error message string on failure. */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeEnqueueInputs(
         nativePointer: Long,
         realInputsPointer: Long,
@@ -487,39 +482,25 @@
     ): String?
 
     /** Returns null on success or an error message string on failure. */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeUpdateShape(nativePointer: Long, currentElapsedTime: Long): String?
 
-    private external fun nativeFinishInput(
-        nativePointer: Long
-    ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun nativeFinishInput(nativePointer: Long)
 
-    private external fun nativeIsInputFinished(
-        nativePointer: Long
-    ): Boolean // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun nativeIsInputFinished(nativePointer: Long): Boolean
 
-    private external fun nativeNeedsUpdate(
-        nativePointer: Long
-    ): Boolean // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun nativeNeedsUpdate(nativePointer: Long): Boolean
 
     /** Returns the native pointer for an `ink::Stroke`, to be wrapped by a [Stroke]. */
-    private external fun nativeCopyToStroke(
-        nativePointer: Long
-    ): Long // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun nativeCopyToStroke(nativePointer: Long): Long
 
-    private external fun nativeInputCount(
-        nativePointer: Long
-    ): Int // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun nativeInputCount(nativePointer: Long): Int
 
-    private external fun nativeRealInputCount(
-        nativePointer: Long
-    ): Int // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun nativeRealInputCount(nativePointer: Long): Int
 
-    private external fun nativePredictedInputCount(
-        nativePointer: Long
-    ): Int // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun nativePredictedInputCount(nativePointer: Long): Int
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeFillInputs(
         nativePointer: Long,
         mutableStrokeInputBatchPointer: Long,
@@ -531,7 +512,7 @@
      * The [toolTypeClass] parameter is passed as a convenience to native JNI code, to avoid it
      * needing to do a reflection-based FindClass lookup.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeGetAndOverwriteInput(
         nativePointer: Long,
         input: StrokeInput,
@@ -539,12 +520,10 @@
         toolTypeClass: Class<InputToolType>,
     )
 
-    private external fun nativeBrushCoatCount(
-        nativePointer: Long
-    ): Int // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun nativeBrushCoatCount(nativePointer: Long): Int
 
     /** Writes the bounding region to [outEnvelope]. */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeGetMeshBounds(
         nativePointer: Long,
         coatIndex: Int,
@@ -552,13 +531,11 @@
     )
 
     /** Returns the number of mesh partitions. */
-    private external fun nativeGetMeshPartitionCount(
-        nativePointer: Long,
-        coatIndex: Int
-    ): Int // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
+    private external fun nativeGetMeshPartitionCount(nativePointer: Long, coatIndex: Int): Int
 
     /** Returns the number of vertices in the mesh at [partitionIndex]. */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeGetVertexCount(
         nativePointer: Long,
         coatIndex: Int,
@@ -569,7 +546,7 @@
      * Returns a direct [ByteBuffer] wrapped around the contents of [RawVertexData] for the mesh at
      * [partitionIndex]. It will be writeable, so be sure to only expose a read-only wrapper of it.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeGetRawVertexData(
         nativePointer: Long,
         coatIndex: Int,
@@ -581,14 +558,14 @@
      * at [partitionIndex]. It will be writeable, so be sure to only expose a read-only wrapper of
      * it.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeGetRawTriangleIndexData(
         nativePointer: Long,
         coatIndex: Int,
         partitionIndex: Int,
     ): ByteBuffer?
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeGetTriangleIndexStride(
         nativePointer: Long,
         coatIndex: Int,
@@ -599,7 +576,7 @@
      * Return the address of a newly allocated copy of the `ink::MeshFormat` belonging to the mesh
      * at [partitionIndex].
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeAllocMeshFormatCopy(
         nativePointer: Long,
         coatIndex: Int,
@@ -607,26 +584,22 @@
     ): Long
 
     /** Writes the updated region to [outEnvelope]. */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeFillUpdatedRegion(nativePointer: Long, outEnvelope: BoxAccumulator)
 
-    private external fun nativeResetUpdatedRegion(
-        nativePointer: Long
-    ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun nativeResetUpdatedRegion(nativePointer: Long)
 
-    private external fun nativeGetOutlineCount(
-        nativePointer: Long,
-        coatIndex: Int
-    ): Int // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
+    private external fun nativeGetOutlineCount(nativePointer: Long, coatIndex: Int): Int
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeGetOutlineVertexCount(
         nativePointer: Long,
         coatIndex: Int,
         outlineIndex: Int,
     ): Int
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     private external fun nativeFillOutlinePosition(
         nativePointer: Long,
         coatIndex: Int,
@@ -636,9 +609,7 @@
     )
 
     /** Release the underlying memory allocated in [nativeCreateInProgressStroke]. */
-    private external fun nativeFreeInProgressStroke(
-        nativePointer: Long
-    ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative private external fun nativeFreeInProgressStroke(nativePointer: Long)
 
     // Companion object gets initialized before anything else.
     public companion object {
diff --git a/ink/ink-strokes/src/jvmAndroidMain/kotlin/androidx/ink/strokes/Stroke.kt b/ink/ink-strokes/src/jvmAndroidMain/kotlin/androidx/ink/strokes/Stroke.kt
index 72c67e7..c373908 100644
--- a/ink/ink-strokes/src/jvmAndroidMain/kotlin/androidx/ink/strokes/Stroke.kt
+++ b/ink/ink-strokes/src/jvmAndroidMain/kotlin/androidx/ink/strokes/Stroke.kt
@@ -21,6 +21,7 @@
 import androidx.ink.brush.ExperimentalInkCustomBrushApi
 import androidx.ink.geometry.PartitionedMesh
 import androidx.ink.nativeloader.NativeLoader
+import androidx.ink.nativeloader.UsedByNative
 
 /**
  * An immutable object comprised of a [StrokeInputBatch] that represents a user-drawn (or sometimes
@@ -182,12 +183,10 @@
         NativeLoader.load()
     }
 
-    external fun createWithBrushAndInputs(
-        brushNativePointer: Long,
-        inputs: Long
-    ): Long // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
+    external fun createWithBrushAndInputs(brushNativePointer: Long, inputs: Long): Long
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun createWithBrushInputsAndShape(
         brushNativePointer: Long,
         inputs: Long,
@@ -198,20 +197,14 @@
      * Returns the address of a new `ink::StrokeInputBatch` that is a shallow copy of the inputs
      * belonging to the `ink::Stroke` given by the [nativeAddress].
      */
-    external fun allocShallowCopyOfInputs(
-        nativeAddress: Long
-    ): Long // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun allocShallowCopyOfInputs(nativeAddress: Long): Long
 
     /**
      * Returns the address of a new `ink::ModeledShape` that is a shallow copy of the shape
      * belonging to the `ink::Stroke` given by the [nativeAddress].
      */
-    external fun allocShallowCopyOfShape(
-        nativeAddress: Long
-    ): Long // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun allocShallowCopyOfShape(nativeAddress: Long): Long
 
     /** Deletes the `ink::Stroke` given by the [nativeAddress]. */
-    external fun free(
-        nativeAddress: Long
-    ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun free(nativeAddress: Long)
 }
diff --git a/ink/ink-strokes/src/jvmAndroidMain/kotlin/androidx/ink/strokes/StrokeInput.kt b/ink/ink-strokes/src/jvmAndroidMain/kotlin/androidx/ink/strokes/StrokeInput.kt
index e3cd9ad..2d30475 100644
--- a/ink/ink-strokes/src/jvmAndroidMain/kotlin/androidx/ink/strokes/StrokeInput.kt
+++ b/ink/ink-strokes/src/jvmAndroidMain/kotlin/androidx/ink/strokes/StrokeInput.kt
@@ -19,6 +19,7 @@
 import androidx.annotation.IntRange
 import androidx.annotation.VisibleForTesting
 import androidx.ink.brush.InputToolType
+import androidx.ink.nativeloader.UsedByNative
 
 /**
  * A single input specifying position, time since the start of the stream, and optionally
@@ -34,30 +35,22 @@
     /** The x-coordinate of the input position in stroke space. */
     public var x: Float = 0F
         private set
-        get // TODO: b/355248266 - @UsedByNative("stroke_input_jni_helper.cc") must go in Proguard
-
-    // config file instead.
+        @UsedByNative get
 
     /** The y-coordinate of the input position in stroke space. */
     public var y: Float = 0F
         private set
-        get // TODO: b/355248266 - @UsedByNative("stroke_input_jni_helper.cc") must go in Proguard
-
-    // config file instead.
+        @UsedByNative get
 
     /** Time elapsed since the start of the stroke. */
     public var elapsedTimeMillis: Long = 0L
         private set
-        get // TODO: b/355248266 - @UsedByNative("stroke_input_jni_helper.cc") must go in Proguard
-
-    // config file instead.
+        @UsedByNative get
 
     /** The input device used to generate this stroke input. */
     public var toolType: InputToolType = InputToolType.UNKNOWN
         private set
-        get // TODO: b/355248266 - @UsedByNative("stroke_input_jni_helper.cc") must go in Proguard
-
-    // config file instead.
+        @UsedByNative get
 
     /**
      * The physical distance in centimeters that the pointer must travel in order to produce an
@@ -70,9 +63,7 @@
      */
     public var strokeUnitLengthCm: Float = NO_STROKE_UNIT_LENGTH
         private set
-        get // TODO: b/355248266 - @UsedByNative("stroke_input_jni_helper.cc") must go in Proguard
-
-    // config file instead.
+        @UsedByNative get
 
     /**
      * Pressure value in the normalized, unitless range of [0, 1] indicating the force exerted
@@ -83,9 +74,7 @@
      */
     public var pressure: Float = NO_PRESSURE
         private set
-        get // TODO: b/355248266 - @UsedByNative("stroke_input_jni_helper.cc") must go in Proguard
-
-    // config file instead.
+        @UsedByNative get
 
     /** Whether the [pressure] field contains a valid pressure value. */
     @get:JvmName("hasPressure")
@@ -101,9 +90,7 @@
      */
     public var tiltRadians: Float = NO_TILT
         private set
-        get // TODO: b/355248266 - @UsedByNative("stroke_input_jni_helper.cc") must go in Proguard
-
-    // config file instead.
+        @UsedByNative get
 
     /** Whether the [tiltRadians] field contains a valid tilt value. */
     @get:JvmName("hasTilt")
@@ -124,9 +111,7 @@
      */
     public var orientationRadians: Float = NO_ORIENTATION
         private set
-        get // TODO: b/355248266 - @UsedByNative("stroke_input_jni_helper.cc") must go in Proguard
-
-    // config file instead.
+        @UsedByNative get
 
     /** Whether the [orientationRadians] field contains a valid orientation value. */
     @get:JvmName("hasOrientation")
@@ -158,8 +143,7 @@
      *   end is along positive x and values increase towards the positive y-axis. Absence of
      *   [orientationRadians] data is represented with [NO_ORIENTATION].
      */
-    // TODO: b/355248266 - @UsedByNative("stroke_input_jni_helper.cc") must go in Proguard config
-    // file instead.
+    @UsedByNative
     @JvmOverloads
     public fun update(
         x: Float,
diff --git a/ink/ink-strokes/src/jvmAndroidMain/kotlin/androidx/ink/strokes/StrokeInputBatch.kt b/ink/ink-strokes/src/jvmAndroidMain/kotlin/androidx/ink/strokes/StrokeInputBatch.kt
index 5af5cc5..0707468 100644
--- a/ink/ink-strokes/src/jvmAndroidMain/kotlin/androidx/ink/strokes/StrokeInputBatch.kt
+++ b/ink/ink-strokes/src/jvmAndroidMain/kotlin/androidx/ink/strokes/StrokeInputBatch.kt
@@ -19,6 +19,7 @@
 import androidx.annotation.RestrictTo
 import androidx.ink.brush.InputToolType
 import androidx.ink.nativeloader.NativeLoader
+import androidx.ink.nativeloader.UsedByNative
 
 /**
  * A read-only view of an object that stores multiple [StrokeInput] values together in a more
@@ -398,50 +399,31 @@
         NativeLoader.load()
     }
 
-    external fun createNativePeer():
-        Long // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun createNativePeer(): Long
 
-    external fun freeNativePeer(
-        nativePointer: Long
-    ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun freeNativePeer(nativePointer: Long)
 
-    external fun getSize(
-        nativePointer: Long
-    ): Int // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun getSize(nativePointer: Long): Int
 
-    external fun getToolType(
-        nativePointer: Long
-    ): Int // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun getToolType(nativePointer: Long): Int
 
-    external fun getDurationMillis(
-        nativePointer: Long
-    ): Long // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun getDurationMillis(nativePointer: Long): Long
 
-    external fun getStrokeUnitLengthCm(
-        nativePointer: Long
-    ): Float // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun getStrokeUnitLengthCm(nativePointer: Long): Float
 
-    external fun hasStrokeUnitLength(
-        nativePointer: Long
-    ): Boolean // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun hasStrokeUnitLength(nativePointer: Long): Boolean
 
-    external fun hasPressure(
-        nativePointer: Long
-    ): Boolean // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun hasPressure(nativePointer: Long): Boolean
 
-    external fun hasTilt(
-        nativePointer: Long
-    ): Boolean // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun hasTilt(nativePointer: Long): Boolean
 
-    external fun hasOrientation(
-        nativePointer: Long
-    ): Boolean // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun hasOrientation(nativePointer: Long): Boolean
 
     /**
      * The [toolTypeClass] parameter is passed as a convenience to native JNI code, to avoid it
      * needing to do a reflection-based FindClass lookup.
      */
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun populate(
         nativePointer: Long,
         index: Int,
@@ -455,11 +437,9 @@
         NativeLoader.load()
     }
 
-    external fun clear(
-        nativePointer: Long
-    ) // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun clear(nativePointer: Long)
 
-    // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative
     external fun appendSingle(
         nativePointer: Long,
         type: Int,
@@ -472,12 +452,7 @@
         orientation: Float,
     ): String?
 
-    external fun appendBatch(
-        nativePointer: Long,
-        addedNativePointer: Long
-    ): String? // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun appendBatch(nativePointer: Long, addedNativePointer: Long): String?
 
-    external fun copy(
-        nativePointer: Long
-    ): Long // TODO: b/355248266 - @Keep must go in Proguard config file instead.
+    @UsedByNative external fun copy(nativePointer: Long): Long
 }
diff --git a/input/input-motionprediction/api/1.0.0-beta05.txt b/input/input-motionprediction/api/1.0.0-beta05.txt
new file mode 100644
index 0000000..b0eef8e
--- /dev/null
+++ b/input/input-motionprediction/api/1.0.0-beta05.txt
@@ -0,0 +1,11 @@
+// Signature format: 4.0
+package androidx.input.motionprediction {
+
+  public interface MotionEventPredictor {
+    method public static androidx.input.motionprediction.MotionEventPredictor newInstance(android.view.View);
+    method public android.view.MotionEvent? predict();
+    method public void record(android.view.MotionEvent);
+  }
+
+}
+
diff --git a/input/input-motionprediction/api/res-1.0.0-beta05.txt b/input/input-motionprediction/api/res-1.0.0-beta05.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/input/input-motionprediction/api/res-1.0.0-beta05.txt
diff --git a/input/input-motionprediction/api/restricted_1.0.0-beta05.txt b/input/input-motionprediction/api/restricted_1.0.0-beta05.txt
new file mode 100644
index 0000000..b0eef8e
--- /dev/null
+++ b/input/input-motionprediction/api/restricted_1.0.0-beta05.txt
@@ -0,0 +1,11 @@
+// Signature format: 4.0
+package androidx.input.motionprediction {
+
+  public interface MotionEventPredictor {
+    method public static androidx.input.motionprediction.MotionEventPredictor newInstance(android.view.View);
+    method public android.view.MotionEvent? predict();
+    method public void record(android.view.MotionEvent);
+  }
+
+}
+
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/SinglePointerPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/SinglePointerPredictor.java
index 5ad7111..0d7627e 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/SinglePointerPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/SinglePointerPredictor.java
@@ -72,11 +72,11 @@
 
     private final DVector2 mLastPosition = new DVector2();
     private long mLastSeenEventTime;
-    private long mLastPredictEventTime;
+    private double mLastPredictEventTime;
     private long mDownEventTime;
-    private List<Float> mReportRates = new LinkedList<>();
+    private List<Double> mReportRates = new LinkedList<>();
     private int mExpectedPredictionSampleSize = -1;
-    private float mReportRateMs = 0;
+    private double mReportRateMs = 0;
 
     private final DVector2 mPosition = new DVector2();
     private final DVector2 mVelocity = new DVector2();
@@ -141,10 +141,10 @@
         // to be used as an estimate.
         if (mReportRates != null && mReportRates.size() < 20) {
             if (mLastSeenEventTime > 0) {
-                float dt = eventTime - mLastSeenEventTime;
+                double dt = eventTime - mLastSeenEventTime;
                 mReportRates.add(dt);
-                float sum = 0;
-                for (float rate : mReportRates) {
+                double sum = 0;
+                for (double rate : mReportRates) {
                     sum += rate;
                 }
                 mReportRateMs = sum / mReportRates.size();
@@ -250,16 +250,6 @@
         int predictionTargetInSamples =
                 (int) Math.ceil(predictionTargetMs / mReportRateMs * confidenceFactor);
 
-        // Predict at least as far in time as the previous prediction.
-        // Otherwise, it may appear that the coordinates are going backwards.
-        if (mLastPredictEventTime > mLastSeenEventTime) {
-            int minimumPredictionSampleSize = (int) Math.floor(
-                    (mLastPredictEventTime - mLastSeenEventTime) / mReportRateMs
-            );
-            if (predictionTargetInSamples < minimumPredictionSampleSize) {
-                predictionTargetInSamples = minimumPredictionSampleSize;
-            }
-        }
         if (mExpectedPredictionSampleSize != -1) {
             // Normally this should always be false as confidenceFactor should be less than 1.0
             if (predictionTargetInSamples > mExpectedPredictionSampleSize) {
@@ -272,9 +262,11 @@
             predictionTargetInSamples = Math.max(predictionTargetInSamples, 1);
         }
 
-        long predictedEventTime = mLastSeenEventTime;
-        int i = 0;
-        for (; i < predictionTargetInSamples; i++) {
+        double predictedEventTime = mLastSeenEventTime;
+        double nextPredictedEventTime = mLastSeenEventTime + mReportRateMs;
+        for (int i = 0;
+                i < predictionTargetInSamples || (nextPredictedEventTime <= mLastPredictEventTime);
+                i++) {
             mAcceleration.a1 += mJank.a1 * JANK_INFLUENCE;
             mAcceleration.a2 += mJank.a2 * JANK_INFLUENCE;
             mVelocity.a1 += mAcceleration.a1 * ACCELERATION_INFLUENCE;
@@ -290,8 +282,6 @@
                 mPressure = 1;
             }
 
-            long nextPredictedEventTime = predictedEventTime + Math.round(mReportRateMs);
-
             // Abort prediction if the pen is to be lifted.
             if (mPredictLift
                     && mPressure < 0.1
@@ -310,7 +300,7 @@
                 predictedEvent =
                         MotionEvent.obtain(
                                 mDownEventTime /* downTime */,
-                                nextPredictedEventTime /* eventTime */,
+                                (long) nextPredictedEventTime /* eventTime */,
                                 MotionEvent.ACTION_MOVE /* action */,
                                 1 /* pointerCount */,
                                 pointerProperties /* pointer properties */,
@@ -324,9 +314,13 @@
                                 0 /* source */,
                                 0 /* flags */);
             } else {
-                predictedEvent.addBatch(nextPredictedEventTime, coords, 0);
+                predictedEvent.addBatch((long) nextPredictedEventTime, coords, 0);
             }
+            // Keep track of the last predicted time
             predictedEventTime = nextPredictedEventTime;
+
+            // Prepare for next iteration
+            nextPredictedEventTime += mReportRateMs;
         }
 
         // Store the last predicted time
diff --git a/libraryversions.toml b/libraryversions.toml
index 9816791..be8ac77 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -7,7 +7,7 @@
 ARCH_CORE = "2.3.0-alpha01"
 ASYNCLAYOUTINFLATER = "1.1.0-alpha02"
 AUTOFILL = "1.3.0-beta01"
-BENCHMARK = "1.4.0-alpha01"
+BENCHMARK = "1.4.0-alpha02"
 BIOMETRIC = "1.4.0-alpha02"
 BLUETOOTH = "1.0.0-alpha02"
 BROWSER = "1.9.0-alpha01"
@@ -18,12 +18,12 @@
 CAMERA_VIEWFINDER = "1.4.0-alpha09"
 CARDVIEW = "1.1.0-alpha01"
 CAR_APP = "1.7.0-beta02"
-COLLECTION = "1.5.0-alpha02"
-COMPOSE = "1.8.0-alpha02"
+COLLECTION = "1.5.0-alpha03"
+COMPOSE = "1.8.0-alpha03"
 COMPOSE_MATERIAL3 = "1.4.0-alpha01"
-COMPOSE_MATERIAL3_ADAPTIVE = "1.1.0-alpha03"
+COMPOSE_MATERIAL3_ADAPTIVE = "1.1.0-alpha04"
 COMPOSE_MATERIAL3_COMMON = "1.0.0-alpha01"
-COMPOSE_RUNTIME_TRACING = "1.0.0-beta01"
+COMPOSE_RUNTIME = "1.8.0-alpha03"
 CONSTRAINTLAYOUT = "2.2.0-beta01"
 CONSTRAINTLAYOUT_COMPOSE = "1.1.0-beta01"
 CONSTRAINTLAYOUT_CORE = "1.1.0-beta01"
@@ -70,7 +70,7 @@
 GRAPHICS_PATH = "1.0.0-rc01"
 GRAPHICS_SHAPES = "1.0.0-rc01"
 GRIDLAYOUT = "1.1.0-beta02"
-HEALTH_CONNECT = "1.1.0-alpha09"
+HEALTH_CONNECT = "1.1.0-alpha10"
 HEALTH_CONNECT_TESTING_QUARANTINE = "1.0.0-alpha01"
 HEALTH_SERVICES_CLIENT = "1.1.0-alpha03"
 HEIFWRITER = "1.1.0-alpha03"
@@ -78,7 +78,7 @@
 HILT_NAVIGATION = "1.2.0-rc01"
 HILT_NAVIGATION_COMPOSE = "1.2.0-rc01"
 INK = "1.0.0-alpha01"
-INPUT_MOTIONPREDICTION = "1.0.0-beta04"
+INPUT_MOTIONPREDICTION = "1.0.0-beta05"
 INSPECTION = "1.0.0"
 INTERPOLATOR = "1.1.0-alpha01"
 JAVASCRIPTENGINE = "1.0.0-beta01"
@@ -90,7 +90,7 @@
 LEANBACK_TAB = "1.1.0-beta01"
 LEGACY = "1.1.0-alpha01"
 LIBYUV = "0.1.0-dev01"
-LIFECYCLE = "2.9.0-alpha03"
+LIFECYCLE = "2.9.0-alpha04"
 LIFECYCLE_EXTENSIONS = "2.2.0"
 LINT = "1.0.0-alpha02"
 LOADER = "1.2.0-alpha01"
@@ -116,7 +116,7 @@
 RECYCLERVIEW_SELECTION = "1.2.0-alpha02"
 REMOTECALLBACK = "1.0.0-alpha02"
 RESOURCEINSPECTION = "1.1.0-alpha01"
-ROOM = "2.7.0-alpha08"
+ROOM = "2.7.0-alpha09"
 SAFEPARCEL = "1.0.0-alpha01"
 SAVEDSTATE = "1.3.0-alpha02"
 SECURITY = "1.1.0-alpha07"
@@ -132,7 +132,7 @@
 SLICE_BUILDERS_KTX = "1.0.0-alpha09"
 SLICE_REMOTECALLBACK = "1.0.0-alpha01"
 SLIDINGPANELAYOUT = "1.3.0-alpha01"
-SQLITE = "2.5.0-alpha08"
+SQLITE = "2.5.0-alpha09"
 SQLITE_INSPECTOR = "2.1.0-alpha01"
 STABLE_AIDL = "1.0.0-alpha01"
 STARTUP = "1.2.0-rc01"
@@ -154,8 +154,8 @@
 VIEWPAGER = "1.1.0-alpha02"
 VIEWPAGER2 = "1.2.0-alpha01"
 WEAR = "1.4.0-alpha01"
-WEAR_COMPOSE = "1.5.0-alpha02"
-WEAR_COMPOSE_MATERIAL3 = "1.0.0-alpha25"
+WEAR_COMPOSE = "1.5.0-alpha03"
+WEAR_COMPOSE_MATERIAL3 = "1.0.0-alpha26"
 WEAR_CORE = "1.0.0-alpha01"
 WEAR_INPUT = "1.2.0-alpha03"
 WEAR_INPUT_TESTING = "1.2.0-alpha03"
@@ -204,7 +204,6 @@
 COMPOSE_MATERIAL3_ADAPTIVE = { group = "androidx.compose.material3.adaptive", atomicGroupVersion = "versions.COMPOSE_MATERIAL3_ADAPTIVE" }
 COMPOSE_MATERIAL3_ADAPTIVE_NAVIGATION_SUITE = { group = "androidx.compose.material3", atomicGroupVersion = "versions.COMPOSE_MATERIAL3", overrideInclude = [ ":compose:material3:material3-adaptive-navigation-suite" ] }
 COMPOSE_RUNTIME = { group = "androidx.compose.runtime", atomicGroupVersion = "versions.COMPOSE" }
-COMPOSE_RUNTIME_TRACING = { group = "androidx.compose.runtime", atomicGroupVersion = "versions.COMPOSE_RUNTIME_TRACING", overrideInclude = [ ":compose:runtime:runtime-tracing" ] }
 COMPOSE_UI = { group = "androidx.compose.ui", atomicGroupVersion = "versions.COMPOSE" }
 CONCURRENT = { group = "androidx.concurrent", atomicGroupVersion = "versions.FUTURES" }
 CONSTRAINTLAYOUT = { group = "androidx.constraintlayout" }
diff --git a/lifecycle/lifecycle-livedata-core-lint/build.gradle b/lifecycle/lifecycle-livedata-core-lint/build.gradle
index 85b76e5..ac05ab8 100644
--- a/lifecycle/lifecycle-livedata-core-lint/build.gradle
+++ b/lifecycle/lifecycle-livedata-core-lint/build.gradle
@@ -29,12 +29,12 @@
 }
 
 dependencies {
-    compileOnly(libs.androidLintApi)
+    compileOnly(libs.androidLintPrevApi)
     compileOnly(libs.kotlinStdlib)
 
     testImplementation(libs.kotlinStdlib)
-    testImplementation(libs.androidLint)
-    testImplementation(libs.androidLintTests)
+    testImplementation(libs.androidLintPrev)
+    testImplementation(libs.androidLintPrevTests)
     testImplementation(libs.junit)
 }
 
diff --git a/lifecycle/lifecycle-runtime-lint/src/main/java/androidx/lifecycle/lint/LifecycleRuntimeIssueRegistry.kt b/lifecycle/lifecycle-runtime-lint/src/main/java/androidx/lifecycle/lint/LifecycleRuntimeIssueRegistry.kt
index 398108f..873d2a4 100644
--- a/lifecycle/lifecycle-runtime-lint/src/main/java/androidx/lifecycle/lint/LifecycleRuntimeIssueRegistry.kt
+++ b/lifecycle/lifecycle-runtime-lint/src/main/java/androidx/lifecycle/lint/LifecycleRuntimeIssueRegistry.kt
@@ -23,7 +23,7 @@
 @Suppress("UnstableApiUsage")
 class LifecycleRuntimeIssueRegistry : IssueRegistry() {
     // tests are run with this version. We ensure that with ApiLintVersionsTest
-    override val api = 14
+    override val api = 16
     override val minApi = CURRENT_API
     override val issues
         get() = listOf(LifecycleWhenChecks.ISSUE, RepeatOnLifecycleDetector.ISSUE)
diff --git a/lifecycle/lifecycle-runtime-testing-lint/src/main/java/androidx/lifecycle/testing/lint/LifecycleRuntimeTestingIssueRegistry.kt b/lifecycle/lifecycle-runtime-testing-lint/src/main/java/androidx/lifecycle/testing/lint/LifecycleRuntimeTestingIssueRegistry.kt
index 1b35d76..76f5493 100644
--- a/lifecycle/lifecycle-runtime-testing-lint/src/main/java/androidx/lifecycle/testing/lint/LifecycleRuntimeTestingIssueRegistry.kt
+++ b/lifecycle/lifecycle-runtime-testing-lint/src/main/java/androidx/lifecycle/testing/lint/LifecycleRuntimeTestingIssueRegistry.kt
@@ -23,7 +23,7 @@
 @Suppress("UnstableApiUsage")
 class LifecycleRuntimeTestingIssueRegistry : IssueRegistry() {
     // tests are run with this version. We ensure that with ApiLintVersionsTest
-    override val api = 14
+    override val api = 16
     override val minApi = CURRENT_API
     override val issues
         get() =
diff --git a/lifecycle/lifecycle-viewmodel-compose-lint/src/main/java/androidx/lifecycle/lint/LifecycleViewModelComposeIssueRegistry.kt b/lifecycle/lifecycle-viewmodel-compose-lint/src/main/java/androidx/lifecycle/lint/LifecycleViewModelComposeIssueRegistry.kt
index 2e1099f..f9bc4b5 100644
--- a/lifecycle/lifecycle-viewmodel-compose-lint/src/main/java/androidx/lifecycle/lint/LifecycleViewModelComposeIssueRegistry.kt
+++ b/lifecycle/lifecycle-viewmodel-compose-lint/src/main/java/androidx/lifecycle/lint/LifecycleViewModelComposeIssueRegistry.kt
@@ -23,7 +23,7 @@
 @Suppress("UnstableApiUsage")
 class LifecycleViewModelComposeIssueRegistry : IssueRegistry() {
     // tests are run with this version. We ensure that with ApiLintVersionsTest
-    override val api = 14
+    override val api = 16
     override val minApi = CURRENT_API
     override val issues
         get() = listOf(ViewModelConstructorInComposableDetector.ISSUE)
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/bcv/native/current.txt b/lifecycle/lifecycle-viewmodel-savedstate/bcv/native/current.txt
new file mode 100644
index 0000000..8ce1950
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel-savedstate/bcv/native/current.txt
@@ -0,0 +1,8 @@
+// Klib ABI Dump
+// Targets: [iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64]
+// Rendering settings:
+// - Signature version: 2
+// - Show manifest properties: true
+// - Show declarations: true
+
+// Library unique name: <androidx.lifecycle:lifecycle-viewmodel-savedstate>
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/build.gradle b/lifecycle/lifecycle-viewmodel-savedstate/build.gradle
index 108a647..b50d0a2 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/build.gradle
+++ b/lifecycle/lifecycle-viewmodel-savedstate/build.gradle
@@ -22,39 +22,154 @@
  * modifying its settings.
  */
 import androidx.build.LibraryType
+import androidx.build.PlatformIdentifier
+import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode
 
 plugins {
     id("AndroidXPlugin")
     id("com.android.library")
-    id("kotlin-android")
 }
 
+androidXMultiplatform {
+    android()
+    desktop()
+    ios()
+    linux()
+    mac()
+
+    defaultPlatform(PlatformIdentifier.ANDROID)
+
+    sourceSets {
+        commonMain {
+            dependencies {
+                api("androidx.annotation:annotation:1.8.1")
+                api(project(":savedstate:savedstate"))
+                api(project(":lifecycle:lifecycle-viewmodel"))
+                api(libs.kotlinStdlib)
+                api(libs.kotlinCoroutinesCore)
+            }
+        }
+
+        commonTest {
+            dependencies {
+                implementation project(":lifecycle:lifecycle-runtime")
+                implementation(libs.kotlinCoroutinesTest)
+            }
+        }
+
+        jvmMain {
+            dependsOn(commonMain)
+        }
+
+        jvmTest {
+            dependsOn(commonTest)
+        }
+
+        androidMain {
+            dependsOn(jvmMain)
+            dependencies {
+                api("androidx.core:core-ktx:1.2.0")
+                api(project(":lifecycle:lifecycle-livedata-core"))
+                api(libs.kotlinCoroutinesAndroid)
+            }
+        }
+
+        androidUnitTest {
+            dependsOn(jvmTest)
+        }
+
+        androidInstrumentedTest {
+            dependsOn(jvmTest)
+            dependencies {
+                implementation project(":lifecycle:lifecycle-livedata-core")
+                implementation ("androidx.fragment:fragment:1.3.0")
+                implementation project(":internal-testutils-runtime")
+                implementation(libs.truth)
+                implementation(libs.testExtJunit)
+                implementation(libs.testCore)
+                implementation(libs.testRunner)
+                implementation(libs.testRules)
+            }
+        }
+
+        nonAndroidMain {
+            dependsOn(commonMain)
+        }
+
+        nonAndroidTest {
+            dependsOn(commonTest)
+        }
+
+        desktopMain {
+            dependsOn(jvmMain)
+            dependsOn(nonAndroidMain)
+        }
+
+        desktopTest {
+            dependsOn(jvmTest)
+            dependsOn(nonAndroidTest)
+        }
+
+        nativeMain {
+            dependsOn(commonMain)
+            dependsOn(nonAndroidMain)
+        }
+
+        nativeTest {
+            dependsOn(commonTest)
+            dependsOn(nonAndroidTest)
+        }
+
+        darwinMain {
+            dependsOn(nativeMain)
+        }
+
+        darwinTest {
+            dependsOn(nativeTest)
+        }
+
+        linuxMain {
+            dependsOn(nativeMain)
+        }
+
+        linuxTest {
+            dependsOn(nativeTest)
+        }
+
+        targets.configureEach { target ->
+            if (target.platformType == org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.native) {
+                target.compilations["main"].defaultSourceSet {
+                    def konanTargetFamily = target.konanTarget.family
+                    if (konanTargetFamily == org.jetbrains.kotlin.konan.target.Family.OSX || konanTargetFamily == org.jetbrains.kotlin.konan.target.Family.IOS) {
+                        dependsOn(darwinMain)
+                    } else if (konanTargetFamily == org.jetbrains.kotlin.konan.target.Family.LINUX) {
+                        dependsOn(linuxMain)
+                    } else {
+                        throw new GradleException("unknown native target ${target}")
+                    }
+                }
+                target.compilations["test"].defaultSourceSet {
+                    def konanTargetFamily = target.konanTarget.family
+                    if (konanTargetFamily == org.jetbrains.kotlin.konan.target.Family.OSX || konanTargetFamily == org.jetbrains.kotlin.konan.target.Family.IOS) {
+                        dependsOn(darwinTest)
+                    } else if (konanTargetFamily == org.jetbrains.kotlin.konan.target.Family.LINUX) {
+                        dependsOn(linuxTest)
+                    } else {
+                        throw new GradleException("unknown native target ${target}")
+                    }
+                }
+            }
+        }
+    }
+}
+
+
 android {
     buildTypes.configureEach {
         consumerProguardFiles("proguard-rules.pro")
     }
     namespace "androidx.lifecycle.viewmodel.savedstate"
-}
-
-dependencies {
-    api("androidx.annotation:annotation:1.8.1")
-    api("androidx.core:core-ktx:1.2.0")
-    api("androidx.savedstate:savedstate:1.2.1")
-    api(project(":lifecycle:lifecycle-livedata-core"))
-    api(project(":lifecycle:lifecycle-viewmodel"))
-    api(libs.kotlinStdlib)
-    api(libs.kotlinCoroutinesAndroid)
-
-    androidTestImplementation project(":lifecycle:lifecycle-runtime")
-    androidTestImplementation project(":lifecycle:lifecycle-livedata-core")
-    androidTestImplementation ("androidx.fragment:fragment:1.3.0")
-    androidTestImplementation project(":internal-testutils-runtime")
-    androidTestImplementation(libs.truth)
-    androidTestImplementation(libs.kotlinStdlib)
-    androidTestImplementation(libs.testExtJunit)
-    androidTestImplementation(libs.testCore)
-    androidTestImplementation(libs.testRunner)
-    androidTestImplementation(libs.testRules)
+    experimentalProperties["android.lint.useK2Uast"] = false // TODO(b/345531033)
 }
 
 androidx {
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/AndroidManifest.xml b/lifecycle/lifecycle-viewmodel-savedstate/src/androidInstrumentedTest/AndroidManifest.xml
similarity index 100%
rename from lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/AndroidManifest.xml
rename to lifecycle/lifecycle-viewmodel-savedstate/src/androidInstrumentedTest/AndroidManifest.xml
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/SavedStateHandleParcelingTest.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/androidInstrumentedTest/kotlin/androidx/lifecycle/SavedStateHandleParcelingTest.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/SavedStateHandleParcelingTest.kt
rename to lifecycle/lifecycle-viewmodel-savedstate/src/androidInstrumentedTest/kotlin/androidx/lifecycle/SavedStateHandleParcelingTest.kt
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/SavedStateHandleProviderTest.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/androidInstrumentedTest/kotlin/androidx/lifecycle/SavedStateHandleProviderTest.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/SavedStateHandleProviderTest.kt
rename to lifecycle/lifecycle-viewmodel-savedstate/src/androidInstrumentedTest/kotlin/androidx/lifecycle/SavedStateHandleProviderTest.kt
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/SavedStateFactoryTest.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/savedstate/SavedStateFactoryTest.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/SavedStateFactoryTest.kt
rename to lifecycle/lifecycle-viewmodel-savedstate/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/savedstate/SavedStateFactoryTest.kt
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/SavedStateHandleSupportTest.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/savedstate/SavedStateHandleSupportTest.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/SavedStateHandleSupportTest.kt
rename to lifecycle/lifecycle-viewmodel-savedstate/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/savedstate/SavedStateHandleSupportTest.kt
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/SavedStateHandleTest.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/savedstate/SavedStateHandleTest.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/SavedStateHandleTest.kt
rename to lifecycle/lifecycle-viewmodel-savedstate/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/savedstate/SavedStateHandleTest.kt
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/TestComponent.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/savedstate/TestComponent.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/TestComponent.kt
rename to lifecycle/lifecycle-viewmodel-savedstate/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/savedstate/TestComponent.kt
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/ViewModelsWithStateTest.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/savedstate/ViewModelsWithStateTest.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/ViewModelsWithStateTest.kt
rename to lifecycle/lifecycle-viewmodel-savedstate/src/androidInstrumentedTest/kotlin/androidx/lifecycle/viewmodel/savedstate/ViewModelsWithStateTest.kt
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/main/AndroidManifest.xml b/lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/AndroidManifest.xml
similarity index 100%
rename from lifecycle/lifecycle-viewmodel-savedstate/src/main/AndroidManifest.xml
rename to lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/AndroidManifest.xml
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/AbstractSavedStateViewModelFactory.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/AbstractSavedStateViewModelFactory.android.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/AbstractSavedStateViewModelFactory.kt
rename to lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/AbstractSavedStateViewModelFactory.android.kt
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/LegacySavedStateHandleController.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/LegacySavedStateHandleController.android.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/LegacySavedStateHandleController.kt
rename to lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/LegacySavedStateHandleController.android.kt
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateHandle.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/SavedStateHandle.android.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateHandle.kt
rename to lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/SavedStateHandle.android.kt
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateHandleController.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/SavedStateHandleController.android.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateHandleController.kt
rename to lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/SavedStateHandleController.android.kt
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateHandleSupport.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/SavedStateHandleSupport.android.kt
similarity index 97%
rename from lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateHandleSupport.kt
rename to lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/SavedStateHandleSupport.android.kt
index 08a09c6..60d9c13 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateHandleSupport.kt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/SavedStateHandleSupport.android.kt
@@ -25,9 +25,6 @@
 import androidx.savedstate.SavedStateRegistry
 import androidx.savedstate.SavedStateRegistryOwner
 
-private const val VIEWMODEL_KEY = "androidx.lifecycle.internal.SavedStateHandlesVM"
-private const val SAVED_STATE_KEY = "androidx.lifecycle.internal.SavedStateHandlesProvider"
-
 /**
  * Enables the support of [SavedStateHandle] in a component.
  *
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateViewModelFactory.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/SavedStateViewModelFactory.android.kt
similarity index 100%
rename from lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateViewModelFactory.kt
rename to lifecycle/lifecycle-viewmodel-savedstate/src/androidMain/kotlin/androidx/lifecycle/SavedStateViewModelFactory.android.kt
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/commonMain/kotlin/androidx/lifecycle/SavedStateHandleSupport.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/commonMain/kotlin/androidx/lifecycle/SavedStateHandleSupport.kt
new file mode 100644
index 0000000..24b5a97
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel-savedstate/src/commonMain/kotlin/androidx/lifecycle/SavedStateHandleSupport.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle
+
+internal const val VIEWMODEL_KEY = "androidx.lifecycle.internal.SavedStateHandlesVM"
+internal const val SAVED_STATE_KEY = "androidx.lifecycle.internal.SavedStateHandlesProvider"
diff --git a/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt b/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt
index b015d75..884d958 100644
--- a/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/AndroidXIssueRegistry.kt
@@ -25,7 +25,7 @@
 
 class AndroidXIssueRegistry : IssueRegistry() {
     override val minApi = CURRENT_API
-    override val api = 14
+    override val api = 16
     override val issues
         get(): List<Issue> {
             return Issues
diff --git a/lint-checks/src/main/java/androidx/build/lint/SampledAnnotationDetector.kt b/lint-checks/src/main/java/androidx/build/lint/SampledAnnotationDetector.kt
index 55966e1..129e48e 100644
--- a/lint-checks/src/main/java/androidx/build/lint/SampledAnnotationDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/SampledAnnotationDetector.kt
@@ -42,6 +42,7 @@
 import com.android.tools.lint.detector.api.Scope
 import com.android.tools.lint.detector.api.Severity
 import com.android.tools.lint.detector.api.SourceCodeScanner
+import org.jetbrains.kotlin.analysis.api.KaExperimentalApi
 import org.jetbrains.kotlin.analysis.api.analyze
 import org.jetbrains.kotlin.kdoc.psi.api.KDoc
 import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection
@@ -239,6 +240,7 @@
  *
  * Checks KDoc in all applicable UDeclarations - this includes classes, functions, fields...
  */
+@OptIn(KaExperimentalApi::class)
 private class KDocSampleLinkHandler(private val context: JavaContext) {
     fun visitDeclaration(node: UDeclaration) {
         val source = node.sourcePsi
@@ -248,7 +250,7 @@
         // expect declaration for analysis.
         if ((source as? KtModifierListOwner)?.hasActualModifier() == true) {
             analyze(source) {
-                val member = (source as? KtDeclaration)?.getSymbol() ?: return
+                val member = (source as? KtDeclaration)?.symbol ?: return
                 val expect = member.getExpectsForActual().singleOrNull() ?: return
                 val declaration = expect.psi ?: return
                 // Recursively handle everything inside the expect declaration, for example if it
diff --git a/lint/lint-gradle/src/test/java/androidx/lint/gradle/ApiLintVersionsTest.kt b/lint/lint-gradle/src/test/java/androidx/lint/gradle/ApiLintVersionsTest.kt
index d25d8e3..af6d6d1 100644
--- a/lint/lint-gradle/src/test/java/androidx/lint/gradle/ApiLintVersionsTest.kt
+++ b/lint/lint-gradle/src/test/java/androidx/lint/gradle/ApiLintVersionsTest.kt
@@ -32,6 +32,6 @@
 
         val registry = GradleIssueRegistry()
         assertThat(registry.api).isEqualTo(CURRENT_API)
-        assertThat(registry.minApi).isEqualTo(14)
+        assertThat(registry.minApi).isEqualTo(16)
     }
 }
diff --git a/navigation/navigation-common-lint/build.gradle b/navigation/navigation-common-lint/build.gradle
index 66c71e5..b913ca6 100644
--- a/navigation/navigation-common-lint/build.gradle
+++ b/navigation/navigation-common-lint/build.gradle
@@ -34,12 +34,12 @@
 
 dependencies {
     compileOnly(libs.kotlinStdlib)
-    compileOnly(libs.androidLintApi)
+    compileOnly(libs.androidLintPrevApi)
     bundleInside(project(":navigation:navigation-lint-common"))
 
     testImplementation(libs.kotlinStdlib)
-    testImplementation(libs.androidLint)
-    testImplementation(libs.androidLintTests)
+    testImplementation(libs.androidLintPrev)
+    testImplementation(libs.androidLintPrevTests)
     testImplementation(libs.junit)
     testImplementation(libs.truth)
 }
diff --git a/navigation/navigation-common-lint/src/main/java/androidx/navigation/common/lint/TypeSafeDestinationMissingAnnotationDetector.kt b/navigation/navigation-common-lint/src/main/java/androidx/navigation/common/lint/TypeSafeDestinationMissingAnnotationDetector.kt
index 8eb5f48..5a2f53c 100644
--- a/navigation/navigation-common-lint/src/main/java/androidx/navigation/common/lint/TypeSafeDestinationMissingAnnotationDetector.kt
+++ b/navigation/navigation-common-lint/src/main/java/androidx/navigation/common/lint/TypeSafeDestinationMissingAnnotationDetector.kt
@@ -16,22 +16,10 @@
 
 package androidx.navigation.common.lint
 
-import androidx.navigation.lint.common.getKClassType
-import com.android.tools.lint.detector.api.Category
-import com.android.tools.lint.detector.api.Detector
-import com.android.tools.lint.detector.api.Implementation
+import androidx.navigation.lint.common.BaseTypeSafeDestinationMissingAnnotationDetector
+import androidx.navigation.lint.common.createMissingKeepAnnotationIssue
+import androidx.navigation.lint.common.createMissingSerializableAnnotationIssue
 import com.android.tools.lint.detector.api.Issue
-import com.android.tools.lint.detector.api.JavaContext
-import com.android.tools.lint.detector.api.Scope
-import com.android.tools.lint.detector.api.Severity
-import com.android.tools.lint.detector.api.SourceCodeScanner
-import com.intellij.psi.PsiClass
-import com.intellij.psi.PsiMethod
-import com.intellij.psi.impl.source.PsiClassReferenceType
-import org.jetbrains.kotlin.psi.KtClassLiteralExpression
-import org.jetbrains.uast.UCallExpression
-import org.jetbrains.uast.getParameterForArgument
-import org.jetbrains.uast.toUElement
 
 /**
  * Checks for missing annotations on type-safe route declarations
@@ -39,127 +27,28 @@
  * Retrieves route classes/objects by tracing KClasses passed as route during NavDestination
  * creation
  */
-class TypeSafeDestinationMissingAnnotationDetector : Detector(), SourceCodeScanner {
+class TypeSafeDestinationMissingAnnotationDetector :
+    BaseTypeSafeDestinationMissingAnnotationDetector(
+        methodNames = listOf("navigation", "deepLink"),
+        constructorNames =
+            listOf(
+                "androidx.navigation.NavDestinationBuilder",
+                "androidx.navigation.NavGraphBuilder"
+            )
+    ) {
+
     companion object {
         val MissingSerializableAnnotationIssue =
-            Issue.create(
-                id = "MissingSerializableAnnotation",
-                briefDescription =
-                    "Type-safe NavDestinations must be annotated with " +
-                        "@kotlinx.serialization.Serializable.",
-                explanation =
-                    "The destination needs to be annotated with @Serializable " +
-                        "in order for Navigation library to convert the class or object declaration " +
-                        "into a NavDestination.",
-                category = Category.CORRECTNESS,
-                severity = Severity.ERROR,
-                implementation =
-                    Implementation(
-                        TypeSafeDestinationMissingAnnotationDetector::class.java,
-                        Scope.JAVA_FILE_SCOPE
-                    )
+            createMissingSerializableAnnotationIssue(
+                TypeSafeDestinationMissingAnnotationDetector::class.java
             )
         val MissingKeepAnnotationIssue =
-            Issue.create(
-                id = "MissingKeepAnnotation",
-                briefDescription =
-                    "In minified builds, Enum classes used as type-safe " +
-                        "Navigation arguments should be annotated with @androidx.annotation.Keep ",
-                explanation =
-                    "Type-safe nav arguments such as Enum types can get " +
-                        "incorrectly obfuscated in minified builds when not referenced directly",
-                category = Category.CORRECTNESS,
-                severity = Severity.WARNING,
-                implementation =
-                    Implementation(
-                        TypeSafeDestinationMissingAnnotationDetector::class.java,
-                        Scope.JAVA_FILE_SCOPE
-                    )
+            createMissingKeepAnnotationIssue(
+                TypeSafeDestinationMissingAnnotationDetector::class.java
             )
     }
 
-    // methods that delegates to NavGraphBuilder/NavDestinationBuilder
-    override fun getApplicableMethodNames(): List<String>? = listOf("navigation")
+    override fun getMissingSerializableIssue(): Issue = MissingSerializableAnnotationIssue
 
-    override fun getApplicableConstructorTypes(): List<String>? =
-        listOf("androidx.navigation.NavDestinationBuilder", "androidx.navigation.NavGraphBuilder")
-
-    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
-        val receiver = node.receiver?.getExpressionType()?.canonicalText ?: return
-        // get the destination type
-        val kClazzType =
-            when (receiver) {
-                // reified version
-                "androidx.navigation.NavGraphBuilder" ->
-                    (node.typeArguments.first() as? PsiClassReferenceType)?.resolve()
-                // route parameter version
-                "androidx.navigation.NavigatorProvider" -> node.getRouteKClassType()
-                else -> return
-            } ?: return
-
-        checkMissingSerializableAnnotation(kClazzType, context)
-
-        // filter for Enums in Class fields
-        val enums = kClazzType.getEnumFields()
-        if (enums.isNotEmpty()) checkMissingKeepAnnotation(enums, context)
-    }
-
-    override fun visitConstructor(
-        context: JavaContext,
-        node: UCallExpression,
-        constructor: PsiMethod
-    ) {
-        val kClazzType = node.getRouteKClassType() ?: return
-        checkMissingSerializableAnnotation(kClazzType, context)
-
-        // filter for Enums in Class fields
-        val enums = kClazzType.getEnumFields()
-        if (enums.isNotEmpty()) checkMissingKeepAnnotation(enums, context)
-    }
-
-    // resolves and returns the actual type of KClass<*>
-    private fun UCallExpression.getRouteKClassType(): PsiClass? {
-        val routeNode =
-            valueArguments.find {
-                getParameterForArgument(it)?.name == "route" &&
-                    it.sourcePsi is KtClassLiteralExpression
-            } ?: return null
-        return routeNode.getKClassType()
-    }
-
-    // check that the Type is annotated with @Serializable
-    private fun checkMissingSerializableAnnotation(kClazz: PsiClass, context: JavaContext) {
-        if (!kClazz.isInterface && !kClazz.hasAnnotation("kotlinx.serialization.Serializable")) {
-            val uElement = kClazz.toUElement() ?: return
-            context.report(
-                MissingSerializableAnnotationIssue,
-                uElement,
-                context.getNameLocation(uElement),
-                """To use this class or object as a type-safe destination, annotate it with @Serializable"""
-            )
-        }
-    }
-
-    private fun PsiClass.getEnumFields(): List<PsiClass> {
-        return fields.mapNotNull {
-            val resolved = (it.type as? PsiClassReferenceType)?.resolve()
-            resolved?.takeIf { resolved.isEnum }
-        }
-    }
-
-    private fun checkMissingKeepAnnotation(fields: List<PsiClass>, context: JavaContext) {
-        fields.onEach {
-            if (!it.hasAnnotation("androidx.annotation.Keep")) {
-                val uElement = it.toUElement() ?: return
-                context.report(
-                    MissingKeepAnnotationIssue,
-                    uElement,
-                    context.getNameLocation(uElement),
-                    """To prevent this Enum's serializer from being obfuscated in minified builds, annotate it with @androidx.annotation.Keep
-                        """
-                        .trimMargin()
-                )
-            }
-        }
-    }
+    override fun getMissingKeepIssue(): Issue = MissingKeepAnnotationIssue
 }
diff --git a/navigation/navigation-common-lint/src/test/java/androidx/navigation/common/lint/MissingKeepAnnotationDetectorTest.kt b/navigation/navigation-common-lint/src/test/java/androidx/navigation/common/lint/MissingKeepAnnotationDetectorTest.kt
index 23c29be..cc7d1b4 100644
--- a/navigation/navigation-common-lint/src/test/java/androidx/navigation/common/lint/MissingKeepAnnotationDetectorTest.kt
+++ b/navigation/navigation-common-lint/src/test/java/androidx/navigation/common/lint/MissingKeepAnnotationDetectorTest.kt
@@ -16,8 +16,8 @@
 
 package androidx.navigation.common.lint
 
+import androidx.navigation.lint.common.KEEP_ANNOTATION
 import androidx.navigation.lint.common.NAVIGATION_STUBS
-import androidx.navigation.lint.common.bytecodeStub
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
@@ -273,50 +273,71 @@
             )
     }
 
-    internal val KEEP_ANNOTATION =
-        bytecodeStub(
-            "Keep.kt",
-            "androidx/annotation",
-            0x2645a498,
-            """
-package androidx.annotation
+    @Test
+    fun testDeeplink_noError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
 
-@Retention(AnnotationRetention.BINARY)
-@Target(
-    AnnotationTarget.FILE,
-    AnnotationTarget.ANNOTATION_CLASS,
-    AnnotationTarget.CLASS,
-    AnnotationTarget.ANNOTATION_CLASS,
-    AnnotationTarget.CONSTRUCTOR,
-    AnnotationTarget.FUNCTION,
-    AnnotationTarget.PROPERTY_GETTER,
-    AnnotationTarget.PROPERTY_SETTER,
-    AnnotationTarget.FIELD
-)
-public annotation class Keep
-        """,
-            """
-                META-INF/main.kotlin_module:
-                H4sIAAAAAAAA/2NgYGBmYGBgBGJOBijgsuUSTsxLKcrPTKnQy0ssy0xPLMnM
-                zxPicsyrLMnIzEv3LhHi90ssc87PKynKz8lJLQIKcAIFPPKLS7xLuCS5uJPz
-                c/VSKxJzC3JShbhCUotLXPNKc71LlBi0GACE4q01cgAAAA==
-                """,
-            """
-                androidx/annotation/Keep.class:
-                H4sIAAAAAAAA/4VSy3ISQRQ9PYRnNIAahcSYGCPxTUy5y4rgoFOSGWroWEWx
-                SHWgKzVhmEkxAyY7dn6G/+HColz6UZZ9JcIsUDenT98+99n3x8+v3wC8wUuG
-                gvC6A9/pXpaF5/mhCB3fK3+Q8iIJxpA7FyNRdoV3VrZOz2UnTCLGsDm3Rpwq
-                M5pEnGGj3vND1/GiEluG0iN2wBAfCXcoGXYX6Oahoh6JQ8Os2C2GtQUuXAzO
-                ZKhUK8J1/U+yOzUEDDv/TDDzW6oZdV11XDFNi1e4YZkn1Xql2VSVXp/LVcts
-                cvu4yi2bIVU7NqskY8g2bKuh27x18k7nXLejlua1JV4z9Ppbhq36wuFF+yz9
-                R9LwXadzdUAjXiictbS9+F13ZV9F4lcXkvrmrYbqO3Gk8/eWKjAbGcD0Kf9n
-                gEcyFF0RCuWl9UcxtUKMIE0ABtZT9kuHbnuKdV8zFCfjVEYraBktt576/lkr
-                TMb72h47nIxJsE9f+bf9U0lUzCTRV72QIdP0h4OOrDmuWpqiPVSj6MuPTuCc
-                unL+nUFJBcaS8kxQUYo//43P8EKdXxCH2mukJNLIYFnRG22kJW5ihSBLkJux
-                PMEtgtsEdwhWcXca4B5yKBBtIy5RxBrBKsE6QZ7gPjZUxgdtxAxsGtgy8BDb
-                iuKRgR08boMFKGG3DS3AkwBPfwGJU24VmQMAAA==
+                import androidx.navigation.*
+                import kotlinx.serialization.*
+                import androidx.annotation.Keep
+
+                @Serializable class TestClass
+                @Keep enum class DeepLinkArg
+                @Serializable class DeepLink(val arg: DeepLinkArg)
+
+                fun navigation() {
+                    val builder = NavDestinationBuilder<NavGraph>(route = TestClass::class)
+                    builder.deepLink<DeepLink>()
+                }
                 """
-        )
+                    )
+                    .indented(),
+                *STUBS,
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testDeeplink_hasError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import kotlinx.serialization.*
+                import androidx.annotation.Keep
+
+                @Serializable class TestClass
+                enum class DeepLinkArg
+                @Serializable class DeepLink(val arg: DeepLinkArg)
+
+                fun navigation() {
+                    val builder = NavDestinationBuilder<NavGraph>(route = TestClass::class)
+                    builder.deepLink<DeepLink>()
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+            )
+            .run()
+            .expect(
+                """
+src/com/example/TestClass.kt:8: Warning: To prevent this Enum's serializer from being obfuscated in minified builds, annotate it with @androidx.annotation.Keep [MissingKeepAnnotation]
+enum class DeepLinkArg
+           ~~~~~~~~~~~
+0 errors, 1 warnings
+            """
+                    .trimIndent()
+            )
+    }
 
     val STUBS = arrayOf(*NAVIGATION_STUBS, KEEP_ANNOTATION)
 }
diff --git a/navigation/navigation-common-lint/src/test/java/androidx/navigation/common/lint/MissingSerializableAnnotationDetectorTest.kt b/navigation/navigation-common-lint/src/test/java/androidx/navigation/common/lint/MissingSerializableAnnotationDetectorTest.kt
index 6ebbc57..32071da 100644
--- a/navigation/navigation-common-lint/src/test/java/androidx/navigation/common/lint/MissingSerializableAnnotationDetectorTest.kt
+++ b/navigation/navigation-common-lint/src/test/java/androidx/navigation/common/lint/MissingSerializableAnnotationDetectorTest.kt
@@ -16,9 +16,11 @@
 
 package androidx.navigation.common.lint
 
+import androidx.navigation.lint.common.K_SERIALIZER
 import androidx.navigation.lint.common.NAVIGATION_STUBS
-import androidx.navigation.lint.common.TEST_CODE_SOURCE
-import androidx.navigation.lint.common.bytecodeStub
+import androidx.navigation.lint.common.SERIALIZABLE_ANNOTATION
+import androidx.navigation.lint.common.SERIALIZABLE_TEST_CLASS
+import androidx.navigation.lint.common.TEST_CLASS
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
@@ -61,7 +63,7 @@
                     )
                     .indented(),
                 *STUBS,
-                TEST_SERIALIZABLE_CLASS
+                SERIALIZABLE_TEST_CLASS.kotlin
             )
             .run()
             .expectClean()
@@ -98,48 +100,48 @@
                     )
                     .indented(),
                 *STUBS,
-                TEST_CLASS
+                TEST_CLASS.kotlin
             )
             .run()
             .expect(
                 """
-src/androidx/test/TestGraph.kt:11: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:11: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 object TestObject
        ~~~~~~~~~~
-src/androidx/test/TestGraph.kt:13: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:13: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 data object TestDataObject
             ~~~~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:15: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:15: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 class TestClass
       ~~~~~~~~~
-src/androidx/test/TestGraph.kt:19: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:19: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 object Outer {
        ~~~~~
-src/androidx/test/TestGraph.kt:20: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:20: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
     data object InnerObject
                 ~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:22: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:22: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
     data class InnerClass (
                ~~~~~~~~~~
-src/androidx/test/TestGraph.kt:29: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:29: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 class InterfaceChildClass(val arg: Boolean): TestInterface
       ~~~~~~~~~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:30: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:30: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 object InterfaceChildObject: TestInterface
        ~~~~~~~~~~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:32: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:32: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 abstract class TestAbstract
                ~~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:33: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:33: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 class AbstractChildClass(val arg: Boolean): TestAbstract()
       ~~~~~~~~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:34: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:34: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 object AbstractChildObject: TestAbstract()
        ~~~~~~~~~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:36: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:36: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 sealed class SealedClass {
              ~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:37: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:37: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
     class SealedSubClass : SealedClass()
           ~~~~~~~~~~~~~~
 13 errors, 0 warnings
@@ -179,7 +181,7 @@
                     )
                     .indented(),
                 *STUBS,
-                TEST_SERIALIZABLE_CLASS
+                SERIALIZABLE_TEST_CLASS.kotlin
             )
             .run()
             .expectClean()
@@ -216,48 +218,48 @@
                     )
                     .indented(),
                 *STUBS,
-                TEST_CLASS
+                TEST_CLASS.kotlin
             )
             .run()
             .expect(
                 """
-src/androidx/test/TestGraph.kt:11: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:11: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 object TestObject
        ~~~~~~~~~~
-src/androidx/test/TestGraph.kt:13: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:13: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 data object TestDataObject
             ~~~~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:15: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:15: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 class TestClass
       ~~~~~~~~~
-src/androidx/test/TestGraph.kt:19: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:19: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 object Outer {
        ~~~~~
-src/androidx/test/TestGraph.kt:20: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:20: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
     data object InnerObject
                 ~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:22: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:22: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
     data class InnerClass (
                ~~~~~~~~~~
-src/androidx/test/TestGraph.kt:29: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:29: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 class InterfaceChildClass(val arg: Boolean): TestInterface
       ~~~~~~~~~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:30: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:30: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 object InterfaceChildObject: TestInterface
        ~~~~~~~~~~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:32: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:32: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 abstract class TestAbstract
                ~~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:33: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:33: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 class AbstractChildClass(val arg: Boolean): TestAbstract()
       ~~~~~~~~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:34: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:34: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 object AbstractChildObject: TestAbstract()
        ~~~~~~~~~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:36: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:36: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 sealed class SealedClass {
              ~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:37: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:37: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
     class SealedSubClass : SealedClass()
           ~~~~~~~~~~~~~~
 13 errors, 0 warnings
@@ -301,7 +303,7 @@
                     )
                     .indented(),
                 *STUBS,
-                TEST_SERIALIZABLE_CLASS
+                SERIALIZABLE_TEST_CLASS.kotlin
             )
             .run()
             .expectClean()
@@ -342,48 +344,48 @@
                     )
                     .indented(),
                 *STUBS,
-                TEST_CLASS
+                TEST_CLASS.kotlin
             )
             .run()
             .expect(
                 """
-src/androidx/test/TestGraph.kt:11: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:11: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 object TestObject
        ~~~~~~~~~~
-src/androidx/test/TestGraph.kt:13: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:13: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 data object TestDataObject
             ~~~~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:15: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:15: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 class TestClass
       ~~~~~~~~~
-src/androidx/test/TestGraph.kt:19: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:19: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 object Outer {
        ~~~~~
-src/androidx/test/TestGraph.kt:20: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:20: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
     data object InnerObject
                 ~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:22: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:22: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
     data class InnerClass (
                ~~~~~~~~~~
-src/androidx/test/TestGraph.kt:29: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:29: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 class InterfaceChildClass(val arg: Boolean): TestInterface
       ~~~~~~~~~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:30: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:30: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 object InterfaceChildObject: TestInterface
        ~~~~~~~~~~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:32: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:32: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 abstract class TestAbstract
                ~~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:33: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:33: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 class AbstractChildClass(val arg: Boolean): TestAbstract()
       ~~~~~~~~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:34: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:34: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 object AbstractChildObject: TestAbstract()
        ~~~~~~~~~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:36: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:36: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 sealed class SealedClass {
              ~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:37: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:37: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
     class SealedSubClass : SealedClass()
           ~~~~~~~~~~~~~~
 13 errors, 0 warnings
@@ -426,7 +428,7 @@
                     )
                     .indented(),
                 *STUBS,
-                TEST_SERIALIZABLE_CLASS
+                SERIALIZABLE_TEST_CLASS.kotlin
             )
             .run()
             .expectClean()
@@ -466,48 +468,48 @@
                     )
                     .indented(),
                 *STUBS,
-                TEST_CLASS
+                TEST_CLASS.kotlin
             )
             .run()
             .expect(
                 """
-src/androidx/test/TestGraph.kt:11: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:11: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 object TestObject
        ~~~~~~~~~~
-src/androidx/test/TestGraph.kt:13: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:13: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 data object TestDataObject
             ~~~~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:15: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:15: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 class TestClass
       ~~~~~~~~~
-src/androidx/test/TestGraph.kt:19: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:19: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 object Outer {
        ~~~~~
-src/androidx/test/TestGraph.kt:20: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:20: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
     data object InnerObject
                 ~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:22: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:22: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
     data class InnerClass (
                ~~~~~~~~~~
-src/androidx/test/TestGraph.kt:29: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:29: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 class InterfaceChildClass(val arg: Boolean): TestInterface
       ~~~~~~~~~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:30: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:30: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 object InterfaceChildObject: TestInterface
        ~~~~~~~~~~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:32: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:32: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 abstract class TestAbstract
                ~~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:33: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:33: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 class AbstractChildClass(val arg: Boolean): TestAbstract()
       ~~~~~~~~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:34: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:34: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 object AbstractChildObject: TestAbstract()
        ~~~~~~~~~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:36: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:36: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
 sealed class SealedClass {
              ~~~~~~~~~~~
-src/androidx/test/TestGraph.kt:37: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+src/androidx/test/Test.kt:37: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
     class SealedSubClass : SealedClass()
           ~~~~~~~~~~~~~~
 13 errors, 0 warnings
@@ -516,80 +518,67 @@
             )
     }
 
-    val SERIALIZABLE_TEST_CODE_SOURCE =
-        """
-package androidx.testSerializable
+    @Test
+    fun testDeeplink_noError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
 
-import kotlinx.serialization.Serializable
+                import androidx.navigation.*
+                import kotlinx.serialization.*
 
-@Serializable class TestClass
-@Serializable object TestObject
-@Serializable data object TestDataObject
-@Serializable object Outer {
-    @Serializable data object InnerObject
-    @Serializable data class InnerClass
-    data class InnerClassNotUsed
-}
+                @Serializable class TestClass
+                @Serializable class DeepLink
 
-// interface should not require @Serializable
-interface TestInterface
-
-@Serializable class InterfaceChildClass: TestInterface
-@Serializable object InterfaceChildObject: TestInterface
-
-@Serializable abstract class TestAbstract
-@Serializable class AbstractChildClass(): TestAbstract()
-@Serializable object AbstractChildObject: TestAbstract()
-
-@Serializable sealed class SealedClass {
-    @Serializable class SealedSubClass : SealedClass()
-}
-        """
-            .trimIndent()
-
-    internal val SERIALIZABLE_ANNOTATION =
-        bytecodeStub(
-            "Serializable.kt",
-            "kotlinx/serialization",
-            0xcce046a,
-            """
-package kotlinx.serialization
-
-import kotlin.reflect.KClass
-
-@MustBeDocumented
-@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS, AnnotationTarget.TYPE)
-public annotation class Serializable(
-    val with: KClass<out KSerializer<*>> = KSerializer::class // Default value indicates that auto-generated serializer is used
-)
-        """,
-            """
-                META-INF/main.kotlin_module:
-                H4sIAAAAAAAA/2NgYGBmYGBgBGJOBijgMuQSTsxLKcrPTKnQy0ssy0xPLMnM
-                zxPi90ssc87PKynKz8lJLfIuEeIECnjkF5d4l3BJcXEn5+fqpVYk5hbkpApx
-                h6QWlzjnJBYXe5coMWgxAADHc03gZwAAAA==
-                """,
-            """
-                kotlinx/serialization/Serializable.class:
-                H4sIAAAAAAAA/4VSW08TQRT+ZntbVoW2VC5F5GrlorYS3yAk3EwaWmjaYoJ9
-                MEM74MKwazqzBfSlb/4Pf4YPpuHRH2U8K7bFsJGHOXMu35n5zuXnr+8/ALzB
-                S4bZM1dL27nMKtG0ubQ/c227TrbStY6kiIExxE95i2cld06y+0enoq5jCDFM
-                9b3ccVx9k7zRU2OIMIQvbP2RYXhhsdCHb0mu1CrD2l3v2nIhmNRul5Vori2t
-                r65T+tz9UELN/0XdJln0lN4U227dOxeOFg2CpQNgVd48EZqCg1xK90I0bhwq
-                +NF+5b08s1TeL+2Uq4cMka3CRqVCDakelnYYZgqB3fuH0nQwpiw0IUgjSKTF
-                pScYMvdAS66061eUECsf7FXzRWIwGZzS4z4bHN+RwidYvfok/AL9aj4cVOi9
-                RLclRaF5g2tOYeO8FaJlY74Y8AUY2Bn5L23fypHWeM3wvtNOWcaYYRnxCavT
-                vlHpdNrm9VdjrNNeMXJscySZMo24kR5MWiZLRpOGGcqFc6HyxF3f9bdoNB0m
-                dMT/YaU/r/+tOvEleqPdMpriWNKqZ3e72zp0G/zqTDMMVOwTh2uvSRNI9Me/
-                LY65JyluVVyvWRdvbUmA8bJHozgX72xl0wN9uMoQSYTp76jfoDANCSZ5Fsky
-                MIAlukN1WGQs/3Et4AXdXwj+gO6HlPiohpDAIIZ8EfdFAkmKDVMsJfAYIxj1
-                1RoMgTEkfDGONCKYoMw8nuQxmcdTTJGK6TxmMFsDU5jDfA1RhWcKGYWYwnMF
-                S8H8Da/SzgtFBAAA
+                fun navigation() {
+                    val builder = NavDestinationBuilder<NavGraph>(route = TestClass::class)
+                    builder.deepLink<DeepLink>()
+                }
                 """
-        )
+                    )
+                    .indented(),
+                *STUBS,
+            )
+            .run()
+            .expectClean()
+    }
 
-    val TEST_CLASS = kotlin(TEST_CODE_SOURCE)
+    @Test
+    fun testDeeplink_hasError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
 
-    val TEST_SERIALIZABLE_CLASS = kotlin(SERIALIZABLE_TEST_CODE_SOURCE)
-    val STUBS = arrayOf(*NAVIGATION_STUBS, SERIALIZABLE_ANNOTATION)
+                import androidx.navigation.*
+                import kotlinx.serialization.*
+
+                @Serializable class TestClass
+                class DeepLink
+
+                fun navigation() {
+                    val builder = NavDestinationBuilder<NavGraph>(route = TestClass::class)
+                    builder.deepLink<DeepLink>()
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+            )
+            .run()
+            .expect(
+                """
+src/com/example/TestClass.kt:7: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+class DeepLink
+      ~~~~~~~~
+1 errors, 0 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
+    val STUBS = arrayOf(*NAVIGATION_STUBS, SERIALIZABLE_ANNOTATION, K_SERIALIZER)
 }
diff --git a/navigation/navigation-common-lint/src/test/java/androidx/navigation/common/lint/WrongStartDestinationTypeDetectorTest.kt b/navigation/navigation-common-lint/src/test/java/androidx/navigation/common/lint/WrongStartDestinationTypeDetectorTest.kt
index 0563946..082c5e9 100644
--- a/navigation/navigation-common-lint/src/test/java/androidx/navigation/common/lint/WrongStartDestinationTypeDetectorTest.kt
+++ b/navigation/navigation-common-lint/src/test/java/androidx/navigation/common/lint/WrongStartDestinationTypeDetectorTest.kt
@@ -17,9 +17,8 @@
 package androidx.navigation.common.lint
 
 import androidx.navigation.lint.common.NAVIGATION_STUBS
-import androidx.navigation.lint.common.TEST_CODE
+import androidx.navigation.lint.common.TEST_CLASS
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest.kotlin
 import com.android.tools.lint.checks.infrastructure.TestMode
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
@@ -636,4 +635,4 @@
         mutableListOf(WrongStartDestinationTypeDetector.WrongStartDestinationType)
 }
 
-private val STUBS = arrayOf(TEST_CODE, *NAVIGATION_STUBS)
+private val STUBS = arrayOf(TEST_CLASS.bytecode, *NAVIGATION_STUBS)
diff --git a/navigation/navigation-compose-lint/build.gradle b/navigation/navigation-compose-lint/build.gradle
index 23b3515..6c48c02 100644
--- a/navigation/navigation-compose-lint/build.gradle
+++ b/navigation/navigation-compose-lint/build.gradle
@@ -41,8 +41,8 @@
     testImplementation(libs.kotlinStdlib)
     testImplementation(libs.kotlinReflect)
     testImplementation(libs.kotlinStdlibJdk8)
-    testImplementation(libs.androidLint)
-    testImplementation(libs.androidLintTests)
+    testImplementation(libs.androidLintPrev)
+    testImplementation(libs.androidLintPrevTests)
     testImplementation(libs.junit)
     testImplementation(libs.truth)
 }
diff --git a/navigation/navigation-compose-lint/src/main/java/androidx/navigation/compose/lint/NavigationComposeIssueRegistry.kt b/navigation/navigation-compose-lint/src/main/java/androidx/navigation/compose/lint/NavigationComposeIssueRegistry.kt
index 337aa59..ed416b93 100644
--- a/navigation/navigation-compose-lint/src/main/java/androidx/navigation/compose/lint/NavigationComposeIssueRegistry.kt
+++ b/navigation/navigation-compose-lint/src/main/java/androidx/navigation/compose/lint/NavigationComposeIssueRegistry.kt
@@ -35,6 +35,8 @@
                 ComposableDestinationInComposeScopeDetector.ComposableNavGraphInComposeScope,
                 UnrememberedGetBackStackEntryDetector.UnrememberedGetBackStackEntry,
                 WrongStartDestinationTypeDetector.WrongStartDestinationType,
+                TypeSafeDestinationMissingAnnotationDetector.MissingKeepAnnotationIssue,
+                TypeSafeDestinationMissingAnnotationDetector.MissingSerializableAnnotationIssue,
             )
 
     override val vendor =
diff --git a/navigation/navigation-compose-lint/src/main/java/androidx/navigation/compose/lint/TypeSafeDestinationMissingAnnotationDetector.kt b/navigation/navigation-compose-lint/src/main/java/androidx/navigation/compose/lint/TypeSafeDestinationMissingAnnotationDetector.kt
new file mode 100644
index 0000000..a3a7072
--- /dev/null
+++ b/navigation/navigation-compose-lint/src/main/java/androidx/navigation/compose/lint/TypeSafeDestinationMissingAnnotationDetector.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.navigation.compose.lint
+
+import androidx.navigation.lint.common.BaseTypeSafeDestinationMissingAnnotationDetector
+import androidx.navigation.lint.common.createMissingKeepAnnotationIssue
+import androidx.navigation.lint.common.createMissingSerializableAnnotationIssue
+import com.android.tools.lint.detector.api.Issue
+
+/**
+ * Checks for missing annotations on type-safe route declarations
+ *
+ * Retrieves route classes/objects by tracing KClasses passed as route during NavDestination
+ * creation
+ */
+class TypeSafeDestinationMissingAnnotationDetector :
+    BaseTypeSafeDestinationMissingAnnotationDetector(
+        methodNames = listOf("composable", "dialog", "navigation"),
+        constructorNames =
+            listOf(
+                "androidx.navigation.compose.ComposeNavigatorDestinationBuilder",
+                "androidx.navigation.compose.DialogNavigatorDestinationBuilder"
+            )
+    ) {
+
+    companion object {
+        val MissingSerializableAnnotationIssue =
+            createMissingSerializableAnnotationIssue(
+                TypeSafeDestinationMissingAnnotationDetector::class.java
+            )
+        val MissingKeepAnnotationIssue =
+            createMissingKeepAnnotationIssue(
+                TypeSafeDestinationMissingAnnotationDetector::class.java
+            )
+    }
+
+    override fun getMissingSerializableIssue(): Issue = MissingSerializableAnnotationIssue
+
+    override fun getMissingKeepIssue(): Issue = MissingKeepAnnotationIssue
+}
diff --git a/navigation/navigation-compose-lint/src/test/java/androidx/navigation/compose/lint/MissingKeepAnnotationDetectorTest.kt b/navigation/navigation-compose-lint/src/test/java/androidx/navigation/compose/lint/MissingKeepAnnotationDetectorTest.kt
new file mode 100644
index 0000000..e7a7ce6
--- /dev/null
+++ b/navigation/navigation-compose-lint/src/test/java/androidx/navigation/compose/lint/MissingKeepAnnotationDetectorTest.kt
@@ -0,0 +1,356 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.navigation.compose.lint
+
+import androidx.navigation.lint.common.KEEP_ANNOTATION
+import androidx.navigation.lint.common.NAVIGATION_STUBS
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class MissingKeepAnnotationDetectorTest : LintDetectorTest() {
+    override fun getDetector(): Detector = TypeSafeDestinationMissingAnnotationDetector()
+
+    override fun getIssues(): List<Issue> =
+        listOf(TypeSafeDestinationMissingAnnotationDetector.MissingKeepAnnotationIssue)
+
+    @Test
+    fun testComposeNavigatorDestinationBuilderConstructor_noError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.annotation.Keep
+                import androidx.navigation.compose.*
+
+                @Keep enum class TestEnum { ONE, TWO }
+                class TestClass(val arg: TestEnum)
+
+                fun navigation() {
+                    ComposeNavigatorDestinationBuilder(route = TestClass::class)
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testComposeNavigatorDestinationBuilderConstructor_hasError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.navigation.compose.*
+
+                enum class TestEnum { ONE, TWO }
+                class TestClass(val arg: TestEnum)
+
+                fun navigation() {
+                    ComposeNavigatorDestinationBuilder(route = TestClass::class)
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+            )
+            .run()
+            .expect(
+                """
+src/com/example/TestEnum.kt:6: Warning: To prevent this Enum's serializer from being obfuscated in minified builds, annotate it with @androidx.annotation.Keep [MissingKeepAnnotation]
+enum class TestEnum { ONE, TWO }
+           ~~~~~~~~
+0 errors, 1 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun testComposable_noError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.navigation.compose.*
+                import androidx.annotation.Keep
+
+                class RouteClass
+                @Keep enum class TestEnum { ONE, TWO }
+                class TestClass(val arg: TestEnum)
+
+                fun navigation() {
+                    val builder = NavGraphBuilder(RouteClass::class)
+                    builder.composable<TestClass>()
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testComposable_hasError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.navigation.compose.*
+
+                class RouteClass
+                enum class TestEnum { ONE, TWO }
+                class TestClass(val arg: TestEnum)
+
+                fun navigation() {
+                    val builder = NavGraphBuilder(RouteClass::class)
+                    builder.composable<TestClass>()
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+            )
+            .run()
+            .expect(
+                """
+src/com/example/RouteClass.kt:7: Warning: To prevent this Enum's serializer from being obfuscated in minified builds, annotate it with @androidx.annotation.Keep [MissingKeepAnnotation]
+enum class TestEnum { ONE, TWO }
+           ~~~~~~~~
+0 errors, 1 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun testNavigation_noError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.navigation.compose.*
+                import androidx.annotation.Keep
+
+                class RouteClass
+                @Keep enum class TestEnum { ONE, TWO }
+                class TestClass(val arg: TestEnum)
+
+                fun navigation() {
+                    val builder = NavGraphBuilder(RouteClass::class)
+                    builder.navigation<TestClass>()
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testNavigation_hasError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.navigation.compose.*
+
+                class RouteClass
+                enum class TestEnum { ONE, TWO }
+                class TestClass(val arg: TestEnum)
+
+                fun navigation() {
+                    val builder = NavGraphBuilder(RouteClass::class)
+                    builder.navigation<TestClass>()
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+            )
+            .run()
+            .expect(
+                """
+src/com/example/RouteClass.kt:7: Warning: To prevent this Enum's serializer from being obfuscated in minified builds, annotate it with @androidx.annotation.Keep [MissingKeepAnnotation]
+enum class TestEnum { ONE, TWO }
+           ~~~~~~~~
+0 errors, 1 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun testDialogNavigatorDestinationBuilderConstructor_noError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.annotation.Keep
+                import androidx.navigation.compose.*
+
+                @Keep enum class TestEnum { ONE, TWO }
+                class TestClass(val arg: TestEnum)
+
+                fun navigation() {
+                    DialogNavigatorDestinationBuilder(route = TestClass::class)
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testDialogNavigatorDestinationBuilderConstructor_hasError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.navigation.compose.*
+
+                enum class TestEnum { ONE, TWO }
+                class TestClass(val arg: TestEnum)
+
+                fun navigation() {
+                    DialogNavigatorDestinationBuilder(route = TestClass::class)
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+            )
+            .run()
+            .expect(
+                """
+src/com/example/TestEnum.kt:6: Warning: To prevent this Enum's serializer from being obfuscated in minified builds, annotate it with @androidx.annotation.Keep [MissingKeepAnnotation]
+enum class TestEnum { ONE, TWO }
+           ~~~~~~~~
+0 errors, 1 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun testDialog_noError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.navigation.compose.*
+                import androidx.annotation.Keep
+
+                class RouteClass
+                @Keep enum class TestEnum { ONE, TWO }
+                class TestClass(val arg: TestEnum)
+
+                fun navigation() {
+                    val builder = NavGraphBuilder(RouteClass::class)
+                    builder.dialog<TestClass>()
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testDialog_hasError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.navigation.compose.*
+
+                class RouteClass
+                enum class TestEnum { ONE, TWO }
+                class TestClass(val arg: TestEnum)
+
+                fun navigation() {
+                    val builder = NavGraphBuilder(RouteClass::class)
+                    builder.dialog<TestClass>()
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+            )
+            .run()
+            .expect(
+                """
+src/com/example/RouteClass.kt:7: Warning: To prevent this Enum's serializer from being obfuscated in minified builds, annotate it with @androidx.annotation.Keep [MissingKeepAnnotation]
+enum class TestEnum { ONE, TWO }
+           ~~~~~~~~
+0 errors, 1 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
+    val STUBS =
+        arrayOf(
+            *NAVIGATION_STUBS,
+            COMPOSE_NAVIGATOR_DESTINATION_BUILDER,
+            DIALOG_NAVIGATOR_DESTINATION_BUILDER,
+            KEEP_ANNOTATION
+        )
+}
diff --git a/navigation/navigation-compose-lint/src/test/java/androidx/navigation/compose/lint/MissingSerializableAnnotationDetectorTest.kt b/navigation/navigation-compose-lint/src/test/java/androidx/navigation/compose/lint/MissingSerializableAnnotationDetectorTest.kt
new file mode 100644
index 0000000..bcf5b51
--- /dev/null
+++ b/navigation/navigation-compose-lint/src/test/java/androidx/navigation/compose/lint/MissingSerializableAnnotationDetectorTest.kt
@@ -0,0 +1,665 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.navigation.compose.lint
+
+import androidx.navigation.lint.common.NAVIGATION_STUBS
+import androidx.navigation.lint.common.SERIALIZABLE_ANNOTATION
+import androidx.navigation.lint.common.SERIALIZABLE_TEST_CLASS
+import androidx.navigation.lint.common.TEST_CLASS
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class MissingSerializableAnnotationDetectorTest : LintDetectorTest() {
+    override fun getDetector(): Detector = TypeSafeDestinationMissingAnnotationDetector()
+
+    override fun getIssues(): List<Issue> =
+        listOf(TypeSafeDestinationMissingAnnotationDetector.MissingSerializableAnnotationIssue)
+
+    @Test
+    fun testComposeNavigatorDestinationBuilderConstructor_noError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.navigation.compose.*
+                import androidx.testSerializable.*
+
+                fun navigation() {
+                    ComposeNavigatorDestinationBuilder(route = TestClass::class)
+                    ComposeNavigatorDestinationBuilder(route = TestObject::class)
+                    ComposeNavigatorDestinationBuilder(route = TestDataObject::class)
+                    ComposeNavigatorDestinationBuilder(route = Outer::class)
+                    ComposeNavigatorDestinationBuilder(route = Outer.InnerObject::class)
+                    ComposeNavigatorDestinationBuilder(route = Outer.InnerClass::class)
+                    ComposeNavigatorDestinationBuilder(route = TestInterface::class)
+                    ComposeNavigatorDestinationBuilder(route = InterfaceChildClass::class)
+                    ComposeNavigatorDestinationBuilder(route = InterfaceChildObject::class)
+                    ComposeNavigatorDestinationBuilder(route = TestAbstract::class)
+                    ComposeNavigatorDestinationBuilder(route = AbstractChildClass::class)
+                    ComposeNavigatorDestinationBuilder(route = AbstractChildObject::class)
+                    ComposeNavigatorDestinationBuilder(route = SealedClass::class)
+                    ComposeNavigatorDestinationBuilder(route = SealedClass.SealedSubClass::class)
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+                SERIALIZABLE_TEST_CLASS.kotlin
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testComposeNavigatorDestinationBuilderConstructor_hasError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.navigation.compose.*
+                import androidx.test.*
+
+                fun navigation() {
+                    ComposeNavigatorDestinationBuilder(route = TestClass::class)
+                    ComposeNavigatorDestinationBuilder(route = TestObject::class)
+                    ComposeNavigatorDestinationBuilder(route = TestDataObject::class)
+                    ComposeNavigatorDestinationBuilder(route = Outer::class)
+                    ComposeNavigatorDestinationBuilder(route = Outer.InnerObject::class)
+                    ComposeNavigatorDestinationBuilder(route = Outer.InnerClass::class)
+                    ComposeNavigatorDestinationBuilder(route = TestInterface::class)
+                    ComposeNavigatorDestinationBuilder(route = InterfaceChildClass::class)
+                    ComposeNavigatorDestinationBuilder(route = InterfaceChildObject::class)
+                    ComposeNavigatorDestinationBuilder(route = TestAbstract::class)
+                    ComposeNavigatorDestinationBuilder(route = AbstractChildClass::class)
+                    ComposeNavigatorDestinationBuilder(route = AbstractChildObject::class)
+                    ComposeNavigatorDestinationBuilder(route = SealedClass::class)
+                    ComposeNavigatorDestinationBuilder(route = SealedClass.SealedSubClass::class)
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+                TEST_CLASS.kotlin
+            )
+            .run()
+            .expect(
+                """
+src/androidx/test/Test.kt:11: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object TestObject
+       ~~~~~~~~~~
+src/androidx/test/Test.kt:13: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+data object TestDataObject
+            ~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:15: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+class TestClass
+      ~~~~~~~~~
+src/androidx/test/Test.kt:19: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object Outer {
+       ~~~~~
+src/androidx/test/Test.kt:20: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+    data object InnerObject
+                ~~~~~~~~~~~
+src/androidx/test/Test.kt:22: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+    data class InnerClass (
+               ~~~~~~~~~~
+src/androidx/test/Test.kt:29: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+class InterfaceChildClass(val arg: Boolean): TestInterface
+      ~~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:30: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object InterfaceChildObject: TestInterface
+       ~~~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:32: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+abstract class TestAbstract
+               ~~~~~~~~~~~~
+src/androidx/test/Test.kt:33: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+class AbstractChildClass(val arg: Boolean): TestAbstract()
+      ~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:34: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object AbstractChildObject: TestAbstract()
+       ~~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:36: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+sealed class SealedClass {
+             ~~~~~~~~~~~
+src/androidx/test/Test.kt:37: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+    class SealedSubClass : SealedClass()
+          ~~~~~~~~~~~~~~
+13 errors, 0 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun testComposable_noError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.navigation.compose.*
+                import androidx.testSerializable.*
+
+                @Serializable class RouteClass
+
+                fun navigation() {
+                    val builder = NavGraphBuilder(route = RouteClass::class)
+
+                    builder.composable<TestClass>()
+                    builder.composable<TestObject>()
+                    builder.composable<TestDataObject>()
+                    builder.composable<Outer>()
+                    builder.composable<Outer.InnerObject>()
+                    builder.composable<Outer.InnerClass>()
+                    builder.composable<TestInterface>()
+                    builder.composable<InterfaceChildClass>()
+                    builder.composable<InterfaceChildObject>()
+                    builder.composable<TestAbstract>()
+                    builder.composable<AbstractChildClass>()
+                    builder.composable<AbstractChildObject>()
+                    builder.composable<SealedClass>()
+                    builder.composable<SealedClass.SealedSubClass>()
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+                SERIALIZABLE_TEST_CLASS.kotlin
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testComposable_hasError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.navigation.compose.*
+                import androidx.test.*
+
+                @Serializable class RouteClass
+
+                fun navigation() {
+                    val builder = NavGraphBuilder(route = RouteClass::class)
+
+                    builder.composable<TestClass>()
+                    builder.composable<TestObject>()
+                    builder.composable<TestDataObject>()
+                    builder.composable<Outer>()
+                    builder.composable<Outer.InnerObject>()
+                    builder.composable<Outer.InnerClass>()
+                    builder.composable<TestInterface>()
+                    builder.composable<InterfaceChildClass>()
+                    builder.composable<InterfaceChildObject>()
+                    builder.composable<TestAbstract>()
+                    builder.composable<AbstractChildClass>()
+                    builder.composable<AbstractChildObject>()
+                    builder.composable<SealedClass>()
+                    builder.composable<SealedClass.SealedSubClass>()
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+                TEST_CLASS.kotlin
+            )
+            .run()
+            .expect(
+                """
+src/androidx/test/Test.kt:11: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object TestObject
+       ~~~~~~~~~~
+src/androidx/test/Test.kt:13: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+data object TestDataObject
+            ~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:15: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+class TestClass
+      ~~~~~~~~~
+src/androidx/test/Test.kt:19: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object Outer {
+       ~~~~~
+src/androidx/test/Test.kt:20: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+    data object InnerObject
+                ~~~~~~~~~~~
+src/androidx/test/Test.kt:22: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+    data class InnerClass (
+               ~~~~~~~~~~
+src/androidx/test/Test.kt:29: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+class InterfaceChildClass(val arg: Boolean): TestInterface
+      ~~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:30: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object InterfaceChildObject: TestInterface
+       ~~~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:32: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+abstract class TestAbstract
+               ~~~~~~~~~~~~
+src/androidx/test/Test.kt:33: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+class AbstractChildClass(val arg: Boolean): TestAbstract()
+      ~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:34: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object AbstractChildObject: TestAbstract()
+       ~~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:36: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+sealed class SealedClass {
+             ~~~~~~~~~~~
+src/androidx/test/Test.kt:37: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+    class SealedSubClass : SealedClass()
+          ~~~~~~~~~~~~~~
+13 errors, 0 warnings
+"""
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun testNavigation_noError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.navigation.compose.*
+                import androidx.testSerializable.*
+
+                @Serializable class RouteClass
+
+                fun navigation() {
+                    val builder = NavGraphBuilder(route = RouteClass::class)
+
+                    builder.navigation<TestClass>()
+                    builder.navigation<TestObject>()
+                    builder.navigation<TestDataObject>()
+                    builder.navigation<Outer>()
+                    builder.navigation<Outer.InnerObject>()
+                    builder.navigation<Outer.InnerClass>()
+                    builder.navigation<TestInterface>()
+                    builder.navigation<InterfaceChildClass>()
+                    builder.navigation<InterfaceChildObject>()
+                    builder.navigation<TestAbstract>()
+                    builder.navigation<AbstractChildClass>()
+                    builder.navigation<AbstractChildObject>()
+                    builder.navigation<SealedClass>()
+                    builder.navigation<SealedClass.SealedSubClass>()
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+                SERIALIZABLE_TEST_CLASS.kotlin
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testNavigation_hasError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.navigation.compose.*
+                import androidx.test.*
+
+                @Serializable class RouteClass
+
+                fun navigation() {
+                    val builder = NavGraphBuilder(route = RouteClass::class)
+
+                    builder.navigation<TestClass>()
+                    builder.navigation<TestObject>()
+                    builder.navigation<TestDataObject>()
+                    builder.navigation<Outer>()
+                    builder.navigation<Outer.InnerObject>()
+                    builder.navigation<Outer.InnerClass>()
+                    builder.navigation<TestInterface>()
+                    builder.navigation<InterfaceChildClass>()
+                    builder.navigation<InterfaceChildObject>()
+                    builder.navigation<TestAbstract>()
+                    builder.navigation<AbstractChildClass>()
+                    builder.navigation<AbstractChildObject>()
+                    builder.navigation<SealedClass>()
+                    builder.navigation<SealedClass.SealedSubClass>()
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+                TEST_CLASS.kotlin
+            )
+            .run()
+            .expect(
+                """
+src/androidx/test/Test.kt:11: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object TestObject
+       ~~~~~~~~~~
+src/androidx/test/Test.kt:13: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+data object TestDataObject
+            ~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:15: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+class TestClass
+      ~~~~~~~~~
+src/androidx/test/Test.kt:19: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object Outer {
+       ~~~~~
+src/androidx/test/Test.kt:20: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+    data object InnerObject
+                ~~~~~~~~~~~
+src/androidx/test/Test.kt:22: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+    data class InnerClass (
+               ~~~~~~~~~~
+src/androidx/test/Test.kt:29: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+class InterfaceChildClass(val arg: Boolean): TestInterface
+      ~~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:30: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object InterfaceChildObject: TestInterface
+       ~~~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:32: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+abstract class TestAbstract
+               ~~~~~~~~~~~~
+src/androidx/test/Test.kt:33: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+class AbstractChildClass(val arg: Boolean): TestAbstract()
+      ~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:34: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object AbstractChildObject: TestAbstract()
+       ~~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:36: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+sealed class SealedClass {
+             ~~~~~~~~~~~
+src/androidx/test/Test.kt:37: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+    class SealedSubClass : SealedClass()
+          ~~~~~~~~~~~~~~
+13 errors, 0 warnings
+"""
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun testDialogNavigatorDestinationBuilderConstructor_noError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.navigation.compose.*
+                import androidx.testSerializable.*
+
+                fun navigation() {
+                    DialogNavigatorDestinationBuilder(route = TestClass::class)
+                    DialogNavigatorDestinationBuilder(route = TestObject::class)
+                    DialogNavigatorDestinationBuilder(route = TestDataObject::class)
+                    DialogNavigatorDestinationBuilder(route = Outer::class)
+                    DialogNavigatorDestinationBuilder(route = Outer.InnerObject::class)
+                    DialogNavigatorDestinationBuilder(route = Outer.InnerClass::class)
+                    DialogNavigatorDestinationBuilder(route = TestInterface::class)
+                    DialogNavigatorDestinationBuilder(route = InterfaceChildClass::class)
+                    DialogNavigatorDestinationBuilder(route = InterfaceChildObject::class)
+                    DialogNavigatorDestinationBuilder(route = TestAbstract::class)
+                    DialogNavigatorDestinationBuilder(route = AbstractChildClass::class)
+                    DialogNavigatorDestinationBuilder(route = AbstractChildObject::class)
+                    DialogNavigatorDestinationBuilder(route = SealedClass::class)
+                    DialogNavigatorDestinationBuilder(route = SealedClass.SealedSubClass::class)
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+                SERIALIZABLE_TEST_CLASS.kotlin
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testDialogNavigatorDestinationBuilderConstructor_hasError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.navigation.compose.*
+                import androidx.test.*
+
+                fun navigation() {
+                    DialogNavigatorDestinationBuilder(route = TestClass::class)
+                    DialogNavigatorDestinationBuilder(route = TestObject::class)
+                    DialogNavigatorDestinationBuilder(route = TestDataObject::class)
+                    DialogNavigatorDestinationBuilder(route = Outer::class)
+                    DialogNavigatorDestinationBuilder(route = Outer.InnerObject::class)
+                    DialogNavigatorDestinationBuilder(route = Outer.InnerClass::class)
+                    DialogNavigatorDestinationBuilder(route = TestInterface::class)
+                    DialogNavigatorDestinationBuilder(route = InterfaceChildClass::class)
+                    DialogNavigatorDestinationBuilder(route = InterfaceChildObject::class)
+                    DialogNavigatorDestinationBuilder(route = TestAbstract::class)
+                    DialogNavigatorDestinationBuilder(route = AbstractChildClass::class)
+                    DialogNavigatorDestinationBuilder(route = AbstractChildObject::class)
+                    DialogNavigatorDestinationBuilder(route = SealedClass::class)
+                    DialogNavigatorDestinationBuilder(route = SealedClass.SealedSubClass::class)
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+                TEST_CLASS.kotlin
+            )
+            .run()
+            .expect(
+                """
+src/androidx/test/Test.kt:11: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object TestObject
+       ~~~~~~~~~~
+src/androidx/test/Test.kt:13: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+data object TestDataObject
+            ~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:15: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+class TestClass
+      ~~~~~~~~~
+src/androidx/test/Test.kt:19: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object Outer {
+       ~~~~~
+src/androidx/test/Test.kt:20: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+    data object InnerObject
+                ~~~~~~~~~~~
+src/androidx/test/Test.kt:22: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+    data class InnerClass (
+               ~~~~~~~~~~
+src/androidx/test/Test.kt:29: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+class InterfaceChildClass(val arg: Boolean): TestInterface
+      ~~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:30: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object InterfaceChildObject: TestInterface
+       ~~~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:32: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+abstract class TestAbstract
+               ~~~~~~~~~~~~
+src/androidx/test/Test.kt:33: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+class AbstractChildClass(val arg: Boolean): TestAbstract()
+      ~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:34: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object AbstractChildObject: TestAbstract()
+       ~~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:36: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+sealed class SealedClass {
+             ~~~~~~~~~~~
+src/androidx/test/Test.kt:37: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+    class SealedSubClass : SealedClass()
+          ~~~~~~~~~~~~~~
+13 errors, 0 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun testDialog_noError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.navigation.compose.*
+                import androidx.testSerializable.*
+
+                @Serializable class RouteClass
+
+                fun navigation() {
+                    val builder = NavGraphBuilder(route = RouteClass::class)
+
+                    builder.dialog<TestClass>()
+                    builder.dialog<TestObject>()
+                    builder.dialog<TestDataObject>()
+                    builder.dialog<Outer>()
+                    builder.dialog<Outer.InnerObject>()
+                    builder.dialog<Outer.InnerClass>()
+                    builder.dialog<TestInterface>()
+                    builder.dialog<InterfaceChildClass>()
+                    builder.dialog<InterfaceChildObject>()
+                    builder.dialog<TestAbstract>()
+                    builder.dialog<AbstractChildClass>()
+                    builder.dialog<AbstractChildObject>()
+                    builder.dialog<SealedClass>()
+                    builder.dialog<SealedClass.SealedSubClass>()
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+                SERIALIZABLE_TEST_CLASS.kotlin
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testDialog_hasError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.navigation.compose.*
+                import androidx.test.*
+
+                @Serializable class RouteClass
+
+                fun navigation() {
+                    val builder = NavGraphBuilder(route = RouteClass::class)
+
+                    builder.dialog<TestClass>()
+                    builder.dialog<TestObject>()
+                    builder.dialog<TestDataObject>()
+                    builder.dialog<Outer>()
+                    builder.dialog<Outer.InnerObject>()
+                    builder.dialog<Outer.InnerClass>()
+                    builder.dialog<TestInterface>()
+                    builder.dialog<InterfaceChildClass>()
+                    builder.dialog<InterfaceChildObject>()
+                    builder.dialog<TestAbstract>()
+                    builder.dialog<AbstractChildClass>()
+                    builder.dialog<AbstractChildObject>()
+                    builder.dialog<SealedClass>()
+                    builder.dialog<SealedClass.SealedSubClass>()
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+                TEST_CLASS.kotlin
+            )
+            .run()
+            .expect(
+                """
+src/androidx/test/Test.kt:11: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object TestObject
+       ~~~~~~~~~~
+src/androidx/test/Test.kt:13: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+data object TestDataObject
+            ~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:15: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+class TestClass
+      ~~~~~~~~~
+src/androidx/test/Test.kt:19: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object Outer {
+       ~~~~~
+src/androidx/test/Test.kt:20: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+    data object InnerObject
+                ~~~~~~~~~~~
+src/androidx/test/Test.kt:22: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+    data class InnerClass (
+               ~~~~~~~~~~
+src/androidx/test/Test.kt:29: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+class InterfaceChildClass(val arg: Boolean): TestInterface
+      ~~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:30: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object InterfaceChildObject: TestInterface
+       ~~~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:32: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+abstract class TestAbstract
+               ~~~~~~~~~~~~
+src/androidx/test/Test.kt:33: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+class AbstractChildClass(val arg: Boolean): TestAbstract()
+      ~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:34: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object AbstractChildObject: TestAbstract()
+       ~~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:36: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+sealed class SealedClass {
+             ~~~~~~~~~~~
+src/androidx/test/Test.kt:37: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+    class SealedSubClass : SealedClass()
+          ~~~~~~~~~~~~~~
+13 errors, 0 warnings
+"""
+                    .trimIndent()
+            )
+    }
+
+    val STUBS =
+        arrayOf(
+            *NAVIGATION_STUBS,
+            COMPOSE_NAVIGATOR_DESTINATION_BUILDER,
+            DIALOG_NAVIGATOR_DESTINATION_BUILDER,
+            SERIALIZABLE_ANNOTATION
+        )
+}
diff --git a/navigation/navigation-compose-lint/src/test/java/androidx/navigation/compose/lint/Stubs.kt b/navigation/navigation-compose-lint/src/test/java/androidx/navigation/compose/lint/Stubs.kt
index ac8406b..44dda35 100644
--- a/navigation/navigation-compose-lint/src/test/java/androidx/navigation/compose/lint/Stubs.kt
+++ b/navigation/navigation-compose-lint/src/test/java/androidx/navigation/compose/lint/Stubs.kt
@@ -259,3 +259,205 @@
                 yRWyBvMGTw0KBksGFfJ/A2uvkva7AwAA
                 """
     )
+
+internal val COMPOSE_NAVIGATOR_DESTINATION_BUILDER =
+    androidx.navigation.lint.common.bytecodeStub(
+        "ComposeNavigatorDestinationBuilder.kt",
+        "androidx/navigation/compose",
+        0xcdeb9868,
+        """
+package androidx.navigation.compose
+
+import kotlin.reflect.KClass
+import androidx.navigation.*
+
+public abstract class Navigator<D : NavDestination>
+
+public open class ComposeNavigator : Navigator<ComposeNavigator.Destination>() {
+    public open class Destination: NavDestination()
+}
+
+public class ComposeNavigatorDestinationBuilder :
+    NavDestinationBuilder<ComposeNavigator.Destination> {
+        public constructor(route: KClass<out Any>)
+    }
+
+public inline fun <reified T : Any> NavGraphBuilder.composable() {}
+
+public inline fun <reified T : Any> NavGraphBuilder.navigation() {}
+            """
+            .trimIndent(),
+        """
+                META-INF/main.kotlin_module:
+                H4sIAAAAAAAA/22MwQrCQAxEI4LQFBFWPOlJevLQP/CgFRQKPfkDi13ahW1S
+                trHUv3drxZOBhDBvZgBgDgCzsBF8B4+41lR6tuWQku5tpcUyKTzRS2pLVS5q
+                Veg+YxLPzhkfhCgIN+4kF8xw+yeePrhpuTMqyaanmBD7i+nE0sd0flpXjn24
+                w+WvRIJBxfdwr163daAbjENdagbdtM6oxchy2cMB3j+iTMHRAAAA
+                """,
+        """
+                androidx/navigation/compose/ComposeNavigator$Destination.class:
+                H4sIAAAAAAAA/51STU8UQRB91cPOwrDyKbgIgmsgAWMYJByMGhJZYzLJykHJ
+                Xjj17nS0s7PdZLqXcNzf4j/wZOKBbDjyo4g1sxzAcNFDv6736vVHVff1ze9L
+                AAdoEN5Ik+ZWpxexkef6m/Tamrhr+2fWqbg5no/HGZtvflTOa1OaqiDCi4dW
+                s/2eLyCE77XR/pAQbO+0a6ggjDCBKmHCf9eO8Lb1v9d4R5hv9azPtIk/Ky9T
+                6SVron8ecIlUwFQBIFCP9QtdsD2O0teEjdEwikRdlGM0nFyuj4b7Yo+OKlc/
+                QjEnCts+YfPB692vkw999S9FVPGEMH1nA8LW35472aOBzlKV7/Y8N61pU0WY
+                bWmjjgf9jspPZCdjZaFluzJry1wX/FasJcaovJlJ5xS3OvpqB3lXfdJFbuXL
+                wHjdV23tNJs/GGN9eZxDA4Kf6LZzxYsxrjKLSw5UXv7C5E8OBNYYw1KcwjPG
+                2tjALOI5wDpjxJpAHSusbpSrnuJ5+Qm5C+ytnSJI8CjBTIJZzHGI+QQLWDwF
+                OTzGEucdIodlh/AP0ebo18ECAAA=
+                """,
+        """
+                androidx/navigation/compose/ComposeNavigator.class:
+                H4sIAAAAAAAA/51STW/TQBB9azux4wSahlISvqFFfIjWoeKA0qoSDUKyFHqg
+                KJectvGqXcXZRd511WN/C/+AExIHVHHkRyHGTtVWPVSlB7+ZefNmZjXjP39/
+                /gLwFssMr7lKMi2Tw0jxA7nHrdQqGuvpV21E1J/Z7VlGZz4Yw3hwWcmpduNS
+                2cXOyx+EsVKVovXNdYZnVxriw2Oobkgl7SaD++LlsIEq/BAVBAye3ZeGYfW/
+                XkKz5wcTbVOpok/C8oRbTpwzPXBpZ6yAWgFgYBPiD2URdclL3jCsHB81Q6ft
+                nH2BEyy2j4/WgpbXcrpOl/WYt1X5/a3qNN2iaI3h+dU2Sq/oXX+pDPVzIcO7
+                63bycZfOc1FxTrCVyzQR2erE0gn6OhEMcwOpxHY+3RXZF76bEtMa6DFPhzyT
+                RXxCNmKlRNZPuTGCDlfbkXvUMs8oFe7oPBuLj7LQdT7nysqpGEojqfC9UtqW
+                ow2ewKHjn9yn+BcIH1AUlTFQefUDte/kOHhIWC3JAI8IGzMBQtTJenhMGBJ3
+                j7Qd1Kh1UeXiaWnvY4lsj/INqrkxghvjZoy5GE3Mk4tWjFtYGIEZ3MbiCBWD
+                usEdg7aBb9D5B1SgkZCGAwAA
+                """,
+        """
+                androidx/navigation/compose/ComposeNavigatorDestinationBuilder.class:
+                H4sIAAAAAAAA/51TTVMTQRB9swnZsCCEKARQ8QPQQJANyMESChHUqpQxWmJx
+                4TRJxjDJZtaamaQ48lv8B560PFiUR3+UZW8SMVVACRx2+nX369e9s72/fn//
+                AWANawybXFV1KKuHvuJtWeNWhsqvhM1PoRH+TteWuplQvxDGStXhbLdkUBXa
+                BWOoFc8SoarT/I0zqef1m+sTWN9cZ1i4cCMXcYbEhlTSbjJMZ4uN0AZS+Vp8
+                DETF+q93Am7M+sIeg39OciNXrPM29wOuav7bcp0yNERUMVsMdc2vC1vWXCrj
+                c6VC2+lu/FJoS60goGEHdNiyIgmPYabXoN5u+lJZoRUP/IKymqplxbgYZhiv
+                HIhKo1f+jmveFERkeJg9PUZfZDcSqdFYwxjBqIdrSDHEspGfQNrDAK4zxO2B
+                NAxbl7r905dKL5U55yIZxv5m3gjLq9xyijnNdow2jUXHYHSAgTUofigjL0+o
+                usLw8vho0nMmnZPn+KgLUwQod3y0mkzH007eybPtTHIiPZKKTXvpRJJRbCAf
+                //k54aQSkdgqQ+7i20gTPr36QrLobZ5ctdzFA4aly1S7yDIM9UkwzP//my03
+                LH3/nbAqGEaLUolSq1kW+gMvBxRJF8MKD/a4lpHfCw7uyhpptDThufctZWVT
+                FFRbGknpk818/m/pGYYLSgndWQVBrrcbtnRFvJKR3FRPYq8r0FeHFTi0oL29
+                oH11EcMSec8o7pAdyqWHvmFs8StufCHXwSM6Ex3qCJYJT3RpGCeEDvKQobzf
+                YSeRJ+tG2oME4tQPRKB/CgtYpFiXFsNqx+bwmOwW5Sdpqql9xAqYLuBmAbdw
+                myBmCriDu/tgBvdwfx+uwYTBrMGcgWeQMZg3cP8AB4jQd18FAAA=
+                """,
+        """
+                androidx/navigation/compose/ComposeNavigatorDestinationBuilderKt.class:
+                H4sIAAAAAAAA/42STU8TQRjH/7OFdruiLS0gRUXFIgWNC8aTIFExysZajW16
+                4TRsx3bodpbsThuOnPw+3owH03j0Qxmf2VYtvkQO87zn97zsfv326TOAB7jL
+                8JirVhTK1omr+EC2uZahcv2wdxzGwt0b6dooE0bPRKylSmqe9mXQEtFLnQFj
+                yB/xAXcDrtru68Mj4VM0xeCMOPwwEAyVSvVvrYj9IuLHnTFve73JsLvTeFj9
+                nbi9e35Aekd3ZLxrw2ZY7oY6kMo9GvRcqbSIFA9cT+lIqlj6cQYOw7zfEX63
+                FupaPwje8Ij3BBUyrFX+HGMiUjeQNnWcwQwuOriASwy5siy/K09uzjw6UNlM
+                dCa8eq516Iq/sj/ok5ExeTI0Wx3v/Epo3uKaE8XqDVL0zZkRWSNAg3WNYVHy
+                RBprk6zWFkNjeFpwhqeOlbdGyohFYy+VyF6yNtmKYw9P89Yi27A2U/Sm9r+8
+                tyk7/c+kYd9nSdsGLf//X+teVzNM7YUtulWuKpWo9XuHImqMrleohj4PmjyS
+                xh8Hs3XZJkY/Ituph/3IF8+lSZTe9pWWPdGUsaTKJ0qFOukVYwsWpsw9YOVL
+                mEaa/NvkPSJtmWNtFLIfkUvtfDAlWCOZpiVszKJC9gLFbPLz5DNTjgKKpNeT
+                6gwh586PLJ5Bzv9ELkwi6W0k1iruJFCGyzT34gFSHkoeljxcwVUP17Ds4Tpu
+                HIDFuImVA6RjTMe4FaOcyGKMue/5/YwICwQAAA==
+                """,
+        """
+                androidx/navigation/compose/Navigator.class:
+                H4sIAAAAAAAA/41Qy27TQBQ9M06cxA3ULa+UdyXKIwscIlZpVYk2QopkikRR
+                NllN4lGYxplBnnHUZb6FP2CFxAJFLPkoxLVTIQRdsLn3nDNn7uvHz6/fALzE
+                LsOe0ElmVHIeabFQU+GU0dHEzD8aK6OTtWSyGhhD76Dfiy+zk60vrVO6pPuH
+                8ZlYiCgVehq9HZ/JidtnCP/Waqgw+AdKK3fI4D19NmzCRy1AFXWGivugLMOT
+                S/v9Mx7V34pnxqVKR2+kE4lwgjQ+X3i0JytCowhgYDPSz1XBOoSSFwzd1TIM
+                eIsHq2WZeEhgtaw/bq2W7XqdXlmbd3iXd7yj6vdPPg8rxc8uFeszPPqfk7Ci
+                9d7xeu7fY//hOMpVmsjs+czR7scmkQybsdLyJJ+PZfZejFNStmMzEelQZKrg
+                F2LjVE2pRp4RDk5Nnk3ka1U87LzLtVNzOVRWkfOV1saVvWxlF5zOfHGQ4uoU
+                7xCLSg5U21/Q+EyA4y5FvxR93KPYXBsQYIOyh/uly8ODMt/GQ8o98jTJc2UE
+                b4CrA2wOEGKLILYHuIbrIzCLG7g5QtViw+KWRctix6L2C0Mu1/6bAgAA
+                """
+    )
+
+internal val DIALOG_NAVIGATOR_DESTINATION_BUILDER =
+    androidx.navigation.lint.common.bytecodeStub(
+        "DialogNavigatorDestinationBuilder.kt",
+        "androidx/navigation/compose",
+        0xe5b79ce2,
+        """
+package androidx.navigation.compose
+
+import kotlin.reflect.KClass
+import androidx.navigation.*
+
+public open class DialogNavigator : Navigator<DialogNavigator.Destination>() {
+    public open class Destination: NavDestination()
+}
+
+public class DialogNavigatorDestinationBuilder :
+    NavDestinationBuilder<DialogNavigator.Destination> {
+        public constructor(route: KClass<out Any>)
+    }
+
+public inline fun <reified T : Any> NavGraphBuilder.dialog() {}
+            """
+            .trimIndent(),
+        """
+                META-INF/main.kotlin_module:
+                H4sIAAAAAAAA/31MTQuCQBCd6OQUBNvRW3Xq4D/wUHoIBA/9gyUXXVhnZHcU
+                +/ct2jF68ODxvgBgCwCbyAS+wByPmhrPtpkz0pNttVgmhTd6S2eprUQdaj0V
+                TOLZOeOjkUTjwUEqwQ7TH/Psxf3AwahLsYp6jdiXJoilpXQfrWuWv3NpteP2
+                bwlT3MXXzMy6H5xR+yePYgqnQ6jkBFf4ABWxV1veAAAA
+                """,
+        """
+                androidx/navigation/compose/DialogNavigator$Destination.class:
+                H4sIAAAAAAAA/51STU8UQRB91cPOwrjKNy5+IYaDaMIgITFGYyIQk0lWDmr2
+                wqnZ6WBlZ7vJdC/huL+Ff+DJxAPZcPRHGWsGDmC46KFfV716lfro/vX75zmA
+                bawSXmubl47z09TqEz7SgZ1Ne25w7LxJ91gX7mj/MuDKtT3jA9ta0wQRnt2W
+                LPIbuogQv2PL4T0her7ebaGBOMEEmoSJ8I094U3nP7t4S5jt9F0o2KafTNC5
+                Dlo4NTiJZECqYKoCEKgv/ClX3qZY+SvCyniUJKqt6jMeTS61x6MttUk7jYuz
+                WM2oSrZFWLu1u5tjStGX/zBDE/cJd67lS5W/JNeCO0MuclNu9INsbNflhjDd
+                YWv2h4NDU37Vh4Uwcx3X00VXl1z5V2Qrs9aUu4X23sieky9uWPbMR65iy5+H
+                NvDAdNmziD9Y60JdzmMVSt7nam/Vcwk+FC+tfaDx4gcmv4uh8EgwrskmHgu2
+                LgWYQiJ3hCeCiXAKbSwLu1JnPcDT+gPKEkTbOkCU4W6GexmmMSMmZjPMYf4A
+                5LGARYl7JB5LHvEfdL2Neb0CAAA=
+                """,
+        """
+                androidx/navigation/compose/DialogNavigator.class:
+                H4sIAAAAAAAA/51STW/TQBB9azt24gSaBigJ3x9BtCBwqCoh2qoSbYVkKe2B
+                olxy2sSrsIqzi7zrqsf+Fv4BJyQOqOLIj0IduxVUPVSlB7+ZefNmZjXj339+
+                /ASwgi7DS66STMvkIFJ8X064lVpFYz37oo2ItiVP9WT3JKGzAIxh1L+o4q92
+                /ULZucbdbWGsVKVmbWON4dmlZgTwGPx1qaTdYHAXlwYN+AhCVFBl8OxnaRhe
+                /c9DaPR8f6ptKlW0IyxPuOXEObN9lxbGCqgVAAY2Jf5AFlGPvOQNjTo6bIZO
+                2/n3VZ3qQvvocLna8lpOz+mxVeZtVn599Z2mWxQtMzy/3D7pFe+uvFKG+pmQ
+                4e0VGwW4w9A9JziT38xlmojs9dTS+rd0Ihjm+lKJ3Xw2EtknPkqJafX1mKcD
+                nskiPiUbsVIi20q5MYKOVtuTE2qZZ5QK93SejcUHWeg6H3Nl5UwMpJFU+F4p
+                bcvRBo/h0OFPj1P8B4T3KYrKGKi8+I7aN3IcPCD0S9LHQ8LGiQAh6mQ9PCIM
+                ibtL2g5q1LqocvGktPfwlOwq5RtUc20IN8b1GHMxmpgnF60YN3BzCGZwCwtD
+                VAzqBrcN2gaBQecYqfVyAIADAAA=
+                """,
+        """
+                androidx/navigation/compose/DialogNavigatorDestinationBuilder.class:
+                H4sIAAAAAAAA/51TXU8TQRQ9sy3dslQoq1DAb6laqLIFSYwBQQFNGms1Ynjh
+                adoOZdrtrNmZNjzyW/wHPml8MMRHf5TxbluxCRCBh5059+vcOzNnf/3+/gPA
+                MpYZnnNVCwNZO/AU78g6NzJQXjVofQq08LYk94N6uRcIwi2hjVTdlI229Gsi
+                tMEY9kqncVDVyfzVU1PPaJcdqF9ZW2GYO3cfG3GGxKpU0qwxzORKzcD4Unmh
+                2PNF1XhvNn2u9crcDoN3RnA1X2rwDvd8rureu0qDIjREVDFbCsK61xCmEnKp
+                tMeVCky3u/bKgSm3fZ+GHQqDthFJOAy3+g0anZYnlRGh4r5XVCakalnVNlIM
+                E9V9UW32y9/zkLcEJTI8zJ0cY8CzHZHUaawURjHm4ArSDLFcZCfgOhjCVYa4
+                2ZeaYf0il3/yTulMmTPukWH8b+StMLzGDSef1erESGYsWoajBQysSf4DGVkF
+                QrVFhldHh1OONWUdf0eHPZgmQLGjw6WkG3etglVgG5nkpDuajs04biLJyDdU
+                iP/8nLDSiYhsiSF/fi3ShM8uLUcWHebpJattPKBJL1BsI8cwMsDAkP3vey00
+                DT39ZlATDGMlqUS53aqI8COv+ORxS0GV+zs8lJHddw5vyzpxtEPC2Q9tZWRL
+                FFVHaknhY1G+/Kd3hlRRKRF2ZSDIdLaDdlgVr2VEN92n2OkRDNRhERZps68J
+                kqqNGB6RtU5+i/aRvDvyDePzX3HtC5kWHtOa6KamsEB4speGCULoIgcZinvd
+                7CQKtNsR9zCBOPUDJdDvhDnMk6+XFsNSd8/jCe0vKD5FU03vIlbETBHXi7iB
+                mwRxq4jbuLMLpnEX93Zha0xqzGpkNRyNjMZ9DfsPJXcGl1gFAAA=
+                """,
+        """
+                androidx/navigation/compose/DialogNavigatorDestinationBuilderKt.class:
+                H4sIAAAAAAAA/41Rz28SQRT+ZqH8ahVKbQXUqpVa2oNLTU8tYtRG3YhohHDh
+                NOyOMLDMNrsD6bEn/x9vxoNpPPpHGd9s0Rg1scnOe9/75pvvzdv59v3zFwAH
+                2GV4zJUXBtI7tRWfyyHXMlC2G0xPgkjYx5L7wbB9sRGExyLSUsWSpzPpeyJ8
+                pdNgDIUxn3Pb52povxmMhUtsgiHlxccZarXWv7qQ74uQn4wWXke7PYZmo3vY
+                +tPtqHl5g1RDj2TUzCDDsDkJtC+VPZ5Pbam0CBX3bUfpUKpIulEaOYZ1dyTc
+                STvQ7Znvv+UhnwoSMuzU/r7Gb0zHmAyp4wpWcCWHZVxlWK7K6vvqz6mZw7BS
+                Nbf5RW1fagyG1dbi5q+F5h7XnDhrOk/QozETsiaAWkwMsGjzVBpUJ+TtMxyc
+                nxVy52c5q2BdJBNKVqVMoGLV2VYuQwqrxPaseoJW8uXXDxlz9iGLbbsM1f++
+                /YOJZkg+CzzBkG9JJdqz6UCEXT7wiSm2Apf7PR5KUy/IbEcOyWMWEs51glno
+                iufSbJTfzZSWU9GTkSTlE6UCHfeKsA8LScTzFspYQorqe1Q9omyZf7FXzH5C
+                PtH4aH4FqhRTNEMGeWwT3iAuQ3UBqxRJjiLWKN+P1WlaOzHaQo3yIWmuUZP1
+                PhIONhxcd1BC2UEFNxzcxK0+WIRN3O5jKTLfnQh347j2A9YTBm1VAwAA
+                """
+    )
diff --git a/navigation/navigation-compose-lint/src/test/java/androidx/navigation/compose/lint/WrongStartDestinationTypeDetectorTest.kt b/navigation/navigation-compose-lint/src/test/java/androidx/navigation/compose/lint/WrongStartDestinationTypeDetectorTest.kt
index 87ade75..9e45093 100644
--- a/navigation/navigation-compose-lint/src/test/java/androidx/navigation/compose/lint/WrongStartDestinationTypeDetectorTest.kt
+++ b/navigation/navigation-compose-lint/src/test/java/androidx/navigation/compose/lint/WrongStartDestinationTypeDetectorTest.kt
@@ -18,7 +18,7 @@
 
 import androidx.compose.lint.test.Stubs
 import androidx.navigation.lint.common.NAVIGATION_STUBS
-import androidx.navigation.lint.common.TEST_CODE
+import androidx.navigation.lint.common.TEST_CLASS
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.checks.infrastructure.TestMode
 import com.android.tools.lint.detector.api.Detector
@@ -262,4 +262,4 @@
         mutableListOf(WrongStartDestinationTypeDetector.WrongStartDestinationType)
 }
 
-private val STUBS = arrayOf(TEST_CODE, COMPOSABLE_NAV_HOST, *NAVIGATION_STUBS)
+private val STUBS = arrayOf(TEST_CLASS.bytecode, COMPOSABLE_NAV_HOST, *NAVIGATION_STUBS)
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/lint-baseline.xml b/navigation/navigation-compose/integration-tests/navigation-demos/lint-baseline.xml
new file mode 100644
index 0000000..dba4441
--- /dev/null
+++ b/navigation/navigation-compose/integration-tests/navigation-demos/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
+
+    <issue
+        id="RestrictedApiAndroidX"
+        message="NavOptionsBuilder.popUpTo can only be called from within the same library group (referenced groupId=`androidx.navigation` from groupId=`androidx.navigation.navigation-compose.integration-tests`)"
+        errorLine1="                        popUpTo(firstScreen) { inclusive = true }"
+        errorLine2="                        ~~~~~~~">
+        <location
+            file="src/main/java/androidx/navigation/compose/demos/NavPopUpToDemo.kt"/>
+    </issue>
+
+</issues>
diff --git a/navigation/navigation-lint-common/src/main/java/androidx/navigation/lint/common/BaseTypeSafeDestinationMissingAnnotationDetector.kt b/navigation/navigation-lint-common/src/main/java/androidx/navigation/lint/common/BaseTypeSafeDestinationMissingAnnotationDetector.kt
new file mode 100644
index 0000000..bd4765c
--- /dev/null
+++ b/navigation/navigation-lint-common/src/main/java/androidx/navigation/lint/common/BaseTypeSafeDestinationMissingAnnotationDetector.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.navigation.lint.common
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.impl.source.PsiClassReferenceType
+import org.jetbrains.kotlin.psi.KtClassLiteralExpression
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.getParameterForArgument
+import org.jetbrains.uast.toUElement
+
+/**
+ * Checks for missing annotations on type-safe route declarations
+ *
+ * Retrieves route classes/objects by tracing KClasses passed as route during NavDestination
+ * creation
+ */
+abstract class BaseTypeSafeDestinationMissingAnnotationDetector(
+    private val methodNames: List<String>,
+    private val constructorNames: List<String>
+) : Detector(), SourceCodeScanner {
+
+    // Issue instantiation requires specific Detector class which is the child class
+    abstract fun getMissingSerializableIssue(): Issue
+
+    abstract fun getMissingKeepIssue(): Issue
+
+    final override fun getApplicableMethodNames(): List<String> = methodNames
+
+    final override fun getApplicableConstructorTypes(): List<String> = constructorNames
+
+    final override fun visitMethodCall(
+        context: JavaContext,
+        node: UCallExpression,
+        method: PsiMethod
+    ) {
+        val receiver = node.receiver?.getExpressionType()?.canonicalText ?: return
+        // get the destination Type
+        val kClazzType =
+            when {
+                // route as parameter
+                node.methodName == "navigation" &&
+                    receiver == "androidx.navigation.NavigatorProvider" -> node.getRouteKClassType()
+                // route as reified Type
+                else -> (node.typeArguments.first() as? PsiClassReferenceType)?.resolve()
+            } ?: return
+
+        checkMissingSerializableAnnotation(kClazzType, context)
+
+        // filter for Enums in Class fields
+        val enums = kClazzType.getEnumFields()
+        if (enums.isNotEmpty()) checkMissingKeepAnnotation(enums, context)
+    }
+
+    final override fun visitConstructor(
+        context: JavaContext,
+        node: UCallExpression,
+        constructor: PsiMethod
+    ) {
+        val kClazzType = node.getRouteKClassType() ?: return
+        checkMissingSerializableAnnotation(kClazzType, context)
+
+        // filter for Enums in Class fields
+        val enums = kClazzType.getEnumFields()
+        if (enums.isNotEmpty()) checkMissingKeepAnnotation(enums, context)
+    }
+
+    // resolves and returns the actual type of KClass<*>
+    private fun UCallExpression.getRouteKClassType(): PsiClass? {
+        val routeNode =
+            valueArguments.find {
+                getParameterForArgument(it)?.name == "route" &&
+                    it.sourcePsi is KtClassLiteralExpression
+            } ?: return null
+        return routeNode.getKClassType()
+    }
+
+    // check that the Type is annotated with @Serializable
+    private fun checkMissingSerializableAnnotation(kClazz: PsiClass, context: JavaContext) {
+        if (!kClazz.isInterface && !kClazz.hasAnnotation("kotlinx.serialization.Serializable")) {
+            val uElement = kClazz.toUElement() ?: return
+            context.report(
+                getMissingSerializableIssue(),
+                uElement,
+                context.getNameLocation(uElement),
+                """To use this class or object as a type-safe destination, annotate it with @Serializable"""
+            )
+        }
+    }
+
+    private fun PsiClass.getEnumFields(): List<PsiClass> {
+        return fields.mapNotNull {
+            val resolved = (it.type as? PsiClassReferenceType)?.resolve()
+            resolved?.takeIf { resolved.isEnum }
+        }
+    }
+
+    private fun checkMissingKeepAnnotation(fields: List<PsiClass>, context: JavaContext) {
+        fields.onEach {
+            if (!it.hasAnnotation("androidx.annotation.Keep")) {
+                val uElement = it.toUElement() ?: return
+                context.report(
+                    getMissingKeepIssue(),
+                    uElement,
+                    context.getNameLocation(uElement),
+                    """To prevent this Enum's serializer from being obfuscated in minified builds, annotate it with @androidx.annotation.Keep
+                        """
+                        .trimMargin()
+                )
+            }
+        }
+    }
+}
+
+fun createMissingSerializableAnnotationIssue(
+    detectorClass: Class<out BaseTypeSafeDestinationMissingAnnotationDetector>
+) =
+    Issue.create(
+        id = "MissingSerializableAnnotation",
+        briefDescription =
+            "Type-safe NavDestinations must be annotated with " +
+                "@kotlinx.serialization.Serializable.",
+        explanation =
+            "The destination needs to be annotated with @Serializable " +
+                "in order for Navigation library to convert the class or object declaration " +
+                "into a NavDestination.",
+        category = Category.CORRECTNESS,
+        severity = Severity.ERROR,
+        implementation = Implementation(detectorClass, Scope.JAVA_FILE_SCOPE)
+    )
+
+fun createMissingKeepAnnotationIssue(
+    detectorClass: Class<out BaseTypeSafeDestinationMissingAnnotationDetector>
+) =
+    Issue.create(
+        id = "MissingKeepAnnotation",
+        briefDescription =
+            "In minified builds, Enum classes used as type-safe " +
+                "Navigation arguments should be annotated with @androidx.annotation.Keep ",
+        explanation =
+            "Type-safe nav arguments such as Enum types can get " +
+                "incorrectly obfuscated in minified builds when not referenced directly",
+        category = Category.CORRECTNESS,
+        severity = Severity.WARNING,
+        implementation = Implementation(detectorClass, Scope.JAVA_FILE_SCOPE)
+    )
diff --git a/navigation/navigation-lint-common/src/main/java/androidx/navigation/lint/common/TestStub.kt b/navigation/navigation-lint-common/src/main/java/androidx/navigation/lint/common/TestStub.kt
index b8f37a8..ae79751 100644
--- a/navigation/navigation-lint-common/src/main/java/androidx/navigation/lint/common/TestStub.kt
+++ b/navigation/navigation-lint-common/src/main/java/androidx/navigation/lint/common/TestStub.kt
@@ -180,7 +180,7 @@
     bytecodeStub(
         "NavDestinationBuilder.kt",
         "androidx/navigation",
-        0xa93da28c,
+        0x3353d8ff,
         """
 package androidx.navigation
 
@@ -191,30 +191,34 @@
     public constructor(
         route: KClass<*>?,
     ): this()
+
+    public inline fun <reified T : Any> deepLink() {}
 }
 """,
         """
                 META-INF/main.kotlin_module:
                 H4sIAAAAAAAA/2NgYGBmYGBgBGJOBijgsuUSTsxLKcrPTKnQy0ssy0xPLMnM
-                zxPicsyrLMnIzEv3LhHi90ssc87PKynKz8lJLQIKcAIFPPKLS7xLuGS4eOHa
-                S1KLS4S4Q4Cke1FiQQZQVpSLOzk/Vy+1IjG3ICdViA0k512ixKDFAABW1JNV
-                jAAAAA==
+                zxPicsyrLMnIzEv3LhHi90ssc87PKynKz8lJLQIKcAIFPPKLS7xLuES5uJPz
+                c/VSKxJzC3JShdhCUkHCSgxaDABdSlZNbgAAAA==
                 """,
         """
                 androidx/navigation/NavDestinationBuilder.class:
-                H4sIAAAAAAAA/41SXU8TQRQ9s0u7y1JlqVIKioKiQk3cSnyREhKBmDRWNGL6
-                wtOUjnVgO5vszDY89rf4D3wy8cE0PvqjjHeWNRoikZf7ce49c+7euz9+fv0G
-                4BnWGTa46qeJ7J9Fio/kgBuZqOiAj/aFNlLl6W4m475IPTCGre39rc7/Ka2d
-                zgkf8SjmahC96Z2IY9NiCC9iHqYYyttSSbPD4K5vdCsowwtQgs8wZT5KzfD4
-                CnrFiKSxtN45TUwsVZSKDzFpRK/2Yq51a6PLsHxJcbuxk9fXOkk6iE6E6aVc
-                Kh1xpRKTC+joIItj3otFK4BjpyulSWYEw8IlegxzvyuvheF9bjhhznDk0uqZ
-                NdPWgIGdEn4mbdakqP+UoTsZ1wOn7gSTce6c0Aa+U2B+rT4ZNwJ/Mg7ZCms4
-                TWfTabq7nl/7/qnshFO7836tWglLS37V8d06a5Zz3LOvb5Lgvv3UK1yR2fHq
-                /1z1k1NDF9pL+rSC2Y5U4iAb9kT63q6IodpJjnnc5am0eQFOH8oBvZGlFK+9
-                y5SRQ9FWI6klld/ylA+FEemLPztnCA6TLD0WL6XlLxac7jnjr0as0k1Kdpvk
-                mf2FyK5SFtn1ki81vmD6c16+R7acgx7uk62cNyDATH6NMiFuTn5OmVOQZy+S
-                g5xcO28oyDYKMUf1tbz7Gh5YGTvCdQrcAnbxMPcreER+j6pVIt44gtvGzTbm
-                2/TsAoWot7GIpSMwjVu4fQRfY0ZjWeOOxl0NTyPUmNOo/AKnk/kd0QMAAA==
+                H4sIAAAAAAAA/41SXU8TQRQ9M0vb7VJkW6WUKgpYtS3qVmJihKaJQIzVikZI
+                X3ia0gEHtrtmd9vwyJM/xH/gk4kPpuHRH2W8sy1BUaLJ5n6fe+7eO99/fP0G
+                4DEeMFSE1w181T12PDFQByJSvudsicGmDCPlxe56X7ldGaTAGFbrm6utf0PW
+                Gq1DMRCOK7wD503nUO5Fawz2xVgKEwzJuvJU1GAwypV2BkmkLCRgMkxE71XI
+                sPwffOMRiaNYbh35kas8J5D7LnE4rzZcEYZrlTbD/CXJerUR50stPzhwDmXU
+                CYTyQkd4nh/FBKGz1Xdd0XHlmgWup0sEfj+SDLOX8DGYXSk/tJR3xDBX31n9
+                cyONsiadKqnSfum8ljUZsmdNX8tIdEUkqB3vDQy6GtMirQWolur5sdJejazu
+                I4bB8GTB4gVuDU9ixW1tmHzkaG3rnJkvDE+qljk8sdkCq/IaX+E1Yz1l5k8/
+                Jbk9sT5j5nMZO1E0c9w0CqyWjOOplzO2WeS19JJlMtsqaKTx4vSjqdlXaKBN
+                vcX/eCAsHn+HofDXSz48iugBbPhd2vA07UVu9XsdGezoCzDkWv6ecNsiUNof
+                B9Pb6oB69AOyS+/6XqR6sukNVKgo/VYEoicjGTw7PymDte33gz35XGn83BjT
+                HiF+KcQinTyhNw6DpqYXSvIueY7+B9KJ6hekP5PBcY9kMg6mUCaZGRXAwmR8
+                sSRFjBj8lDw+Bk9fBFsxOD8qGIO1ZSNL+UpcPYWqptEjXAHsLHLEyePeT8a9
+                DaP+e2dO9GedOfnXMBPzGWOOUeer5I8sA8uxvoP7pFtUm6cpZndhNFFoYq6J
+                Iq6TiRtNzOPmLliIW1jYxWSov8UQSyFuh0iFsENkQ2RC5OJI6Se1hWe7hgQA
+                AA==
                 """
     )
 
@@ -436,6 +440,118 @@
 """
     )
 
+val SERIALIZABLE_ANNOTATION =
+    bytecodeStub(
+        "Serializable.kt",
+        "kotlinx/serialization",
+        0x699c84c1,
+        """
+package kotlinx.serialization
+
+import kotlin.reflect.KClass
+
+@MustBeDocumented
+@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS, AnnotationTarget.TYPE)
+public annotation class Serializable
+        """,
+        """
+                META-INF/main.kotlin_module:
+                H4sIAAAAAAAA/2NgYGBmYGBgBGJOBijgMuQSTsxLKcrPTKnQy0ssy0xPLMnM
+                zxPi90ssc87PKynKz8lJLfIuEeIECnjkF5d4l3BJcXEn5+fqpVYk5hbkpApx
+                h6QWlzjnJBYXe5coMWgxAADHc03gZwAAAA==
+                """,
+        """
+                kotlinx/serialization/Serializable.class:
+                H4sIAAAAAAAA/4VSW08TQRT+ZntbVoW2VC5F5GrlorYS3yAk3EwaWmjaYoJ9
+                MEM74MKwazqzBfSlb/4Pf4YPpuHRH2U8K7bFsJGHOXMu35n5zuXnr+8/ALzB
+                S4bZM1dL27nMKtG0ubQ/c227TrbStY6kiIExxE95i2cld06y+0enoq5jCDFM
+                9b3ccVx9k7zRU2OIMIQvbP2RYXhhsdCHb0mu1CrD2l3v2nIhmNRul5Vori2t
+                r65T+tz9UELN/0XdJln0lN4U227dOxeOFg2CpQNgVd48EZqCg1xK90I0bhwq
+                +NF+5b08s1TeL+2Uq4cMka3CRqVCDakelnYYZgqB3fuH0nQwpiw0IUgjSKTF
+                pScYMvdAS66061eUECsf7FXzRWIwGZzS4z4bHN+RwidYvfok/AL9aj4cVOi9
+                RLclRaF5g2tOYeO8FaJlY74Y8AUY2Bn5L23fypHWeM3wvtNOWcaYYRnxCavT
+                vlHpdNrm9VdjrNNeMXJscySZMo24kR5MWiZLRpOGGcqFc6HyxF3f9bdoNB0m
+                dMT/YaU/r/+tOvEleqPdMpriWNKqZ3e72zp0G/zqTDMMVOwTh2uvSRNI9Me/
+                LY65JyluVVyvWRdvbUmA8bJHozgX72xl0wN9uMoQSYTp76jfoDANCSZ5Fsky
+                MIAlukN1WGQs/3Et4AXdXwj+gO6HlPiohpDAIIZ8EfdFAkmKDVMsJfAYIxj1
+                1RoMgTEkfDGONCKYoMw8nuQxmcdTTJGK6TxmMFsDU5jDfA1RhWcKGYWYwnMF
+                S8H8Da/SzgtFBAAA
+                """
+    )
+
+val KEEP_ANNOTATION =
+    bytecodeStub(
+        "Keep.kt",
+        "androidx/annotation",
+        0x2645a498,
+        """
+package androidx.annotation
+
+@Retention(AnnotationRetention.BINARY)
+@Target(
+    AnnotationTarget.FILE,
+    AnnotationTarget.ANNOTATION_CLASS,
+    AnnotationTarget.CLASS,
+    AnnotationTarget.ANNOTATION_CLASS,
+    AnnotationTarget.CONSTRUCTOR,
+    AnnotationTarget.FUNCTION,
+    AnnotationTarget.PROPERTY_GETTER,
+    AnnotationTarget.PROPERTY_SETTER,
+    AnnotationTarget.FIELD
+)
+public annotation class Keep
+        """,
+        """
+                META-INF/main.kotlin_module:
+                H4sIAAAAAAAA/2NgYGBmYGBgBGJOBijgsuUSTsxLKcrPTKnQy0ssy0xPLMnM
+                zxPicsyrLMnIzEv3LhHi90ssc87PKynKz8lJLQIKcAIFPPKLS7xLuCS5uJPz
+                c/VSKxJzC3JShbhCUotLXPNKc71LlBi0GACE4q01cgAAAA==
+                """,
+        """
+                androidx/annotation/Keep.class:
+                H4sIAAAAAAAA/4VSy3ISQRQ9PYRnNIAahcSYGCPxTUy5y4rgoFOSGWroWEWx
+                SHWgKzVhmEkxAyY7dn6G/+HColz6UZZ9JcIsUDenT98+99n3x8+v3wC8wUuG
+                gvC6A9/pXpaF5/mhCB3fK3+Q8iIJxpA7FyNRdoV3VrZOz2UnTCLGsDm3Rpwq
+                M5pEnGGj3vND1/GiEluG0iN2wBAfCXcoGXYX6Oahoh6JQ8Os2C2GtQUuXAzO
+                ZKhUK8J1/U+yOzUEDDv/TDDzW6oZdV11XDFNi1e4YZkn1Xql2VSVXp/LVcts
+                cvu4yi2bIVU7NqskY8g2bKuh27x18k7nXLejlua1JV4z9Ppbhq36wuFF+yz9
+                R9LwXadzdUAjXiictbS9+F13ZV9F4lcXkvrmrYbqO3Gk8/eWKjAbGcD0Kf9n
+                gEcyFF0RCuWl9UcxtUKMIE0ABtZT9kuHbnuKdV8zFCfjVEYraBktt576/lkr
+                TMb72h47nIxJsE9f+bf9U0lUzCTRV72QIdP0h4OOrDmuWpqiPVSj6MuPTuCc
+                unL+nUFJBcaS8kxQUYo//43P8EKdXxCH2mukJNLIYFnRG22kJW5ihSBLkJux
+                PMEtgtsEdwhWcXca4B5yKBBtIy5RxBrBKsE6QZ7gPjZUxgdtxAxsGtgy8BDb
+                iuKRgR08boMFKGG3DS3AkwBPfwGJU24VmQMAAA==
+                """
+    )
+
+val K_SERIALIZER =
+    bytecodeStub(
+        "KSerializer.kt",
+        "kotlinx/serialization",
+        0xdfbaa177,
+        """
+package kotlinx.serialization
+
+public interface KSerializer<T>
+        """,
+        """
+                META-INF/main.kotlin_module:
+                H4sIAAAAAAAA/2NgYGBmYGBgBGJOBijgsuUSTsxLKcrPTKnQy0ssy0xPLMnM
+                zxPicsyrLMnIzEv3LhHi90ssc87PKynKz8lJLQIKcAIFPPKLS7xLuES5uJPz
+                c/VSKxJzC3JShdhCUkHCSgxaDABdSlZNbgAAAA==
+                """,
+        """
+                kotlinx/serialization/KSerializer.class:
+                H4sIAAAAAAAA/4VQO0/DMBi8zy19hFfKs0wIsSAGUiomQEgsSBFFSLRi6eS2
+                pnKbOlLsVhVTfhcDysyPQnxpGRAMeLj77nzy2f74fHsHcIE64Wgcu0ibeWBV
+                omWkX6XTsQnu299SJWUQ4eS6c9kayZkMImmGwWNvpPru6uavRfB/e2UUCbXW
+                sih4UE4OpJOcFJNZge9BOVRzAIHG7M91rho8Dc4Jh1nqeaIuPGbhZ2nlpZ6l
+                p8VKlvrUFA2Rx5qE49a/T+FO6lBes/HDPRs7QrWth0a6aaIIXjueJn11pyMW
+                B09T4/REPWure5G6NSZ2i4NtiYuxguUqYJdRMO8teAf7iy8mlDhT7qIQohKi
+                GsLDKo9YC7GOjS7IYhM+71vULLYstr8A6rZa9Z8BAAA=
+                """
+    )
+
 val NAVIGATION_STUBS =
     arrayOf(
         NAV_CONTROLLER,
@@ -449,8 +565,48 @@
         NAV_PROVIDER
     )
 
-val TEST_CODE_SOURCE =
-    """
+val SERIALIZABLE_TEST_CLASS =
+    kotlinAndBytecodeStub(
+        "TestSerializable.kt",
+        "androidx/testSerializable",
+        0xdfbaa178,
+        """
+package androidx.testSerializable
+
+import kotlinx.serialization.Serializable
+
+@Serializable class TestClass
+@Serializable object TestObject
+@Serializable data object TestDataObject
+@Serializable object Outer {
+    @Serializable data object InnerObject
+    @Serializable class InnerClass
+    class InnerClassNotUsed
+}
+
+// interface should not require @Serializable
+interface TestInterface
+
+@Serializable class InterfaceChildClass: TestInterface
+@Serializable object InterfaceChildObject: TestInterface
+
+@Serializable abstract class TestAbstract
+@Serializable class AbstractChildClass(): TestAbstract()
+@Serializable object AbstractChildObject: TestAbstract()
+
+@Serializable sealed class SealedClass {
+    @Serializable class SealedSubClass : SealedClass()
+}
+        """
+            .trimIndent()
+    )
+
+val TEST_CLASS =
+    kotlinAndBytecodeStub(
+        "Test.kt",
+        "androidx/test",
+        0x1ed6fc53,
+        """
 package androidx.test
 
 val classInstanceRef = TestClass()
@@ -510,21 +666,14 @@
 class AbstractChildClassComp(val arg: Boolean): TestAbstractComp() { companion object }
 object AbstractChildObjectComp: TestAbstractComp()
     """
-        .trimIndent()
-
-val TEST_CODE =
-    kotlinAndBytecodeStub(
-            "Test.kt",
-            "androidx/test",
-            0x1ed6fc53,
-            TEST_CODE_SOURCE,
-            """
+            .trimIndent(),
+        """
 META-INF/main.kotlin_module:
 H4sIAAAAAAAA/2NgYGBmYGBgBGJOBijgMuQSTsxLKcrPTKnQy0ssy0xPLMnM
 zxPi90ssc87PKynKz8lJLfIuEeIECnjkF5d4l3CJc/HCtZSkFpcIsYWkgiSU
 GLQYABRWGrdkAAAA
 """,
-            """
+        """
 androidx/test/AbstractChildClass.class:
 H4sIAAAAAAAA/4VQTWsTURQ9781XMk3MJH6labW1KrRZmLS4U4ppQAhMu6gl
 i2T1khnaRyYzMO9Fusxvce1GUAQXElz6o8T7JlUQFBfv3HvuO5z78f3Hl68A
@@ -538,7 +687,7 @@
 f7WprAU3NhyPC9zBE4qviiEd3B7DGuDOAHcH1PY+pWgOsInWGExhC9tjeAo1
 hQcKvsJDBVchUKj/BOsI9uW0AgAA
 """,
-            """
+        """
 androidx/test/AbstractChildClassComp$Companion.class:
 H4sIAAAAAAAA/5VSTW/TQBB9u07jxARIWz4SvgolSC0SdRJxK0IqQUiRUpCg
 yqUHtLG3dBN7jbybqMec+CH8g56QOKCoR34UYtYJcKWX2Zn35s143/rnr+8/
@@ -553,7 +702,7 @@
 mnhcLHyAVvFLkwfUWz+G18d6Hxt9bOIGpbjZp5m3j8EMGmgSbxAY3DEo/wZN
 V6SgDwMAAA==
 """,
-            """
+        """
 androidx/test/AbstractChildClassComp.class:
 H4sIAAAAAAAA/41SW08TQRT+ZrfXZZFSEQt4QUEsVdlCfBJCgjWaJoUHJE2E
 p2k7wtDtrNmZEh75LT77QtSQaGKIj/4o45lthRiN4WHPmXP2O9+5/vj55RuA
@@ -571,7 +720,7 @@
 Y9WEu0INAzXy3ybGO3tw67hbx2wd93CfnpirYx4P9sA0FvBwDzmNMY2yhqex
 qJHRKGiMa5R+ATUXZWovBAAA
 """,
-            """
+        """
 androidx/test/AbstractChildObject.class:
 H4sIAAAAAAAA/4VSXWvUQBQ9M7ubZNPV1vrR3bZ+1PqgPpi2+GYR1kUhECPY
 ZaH0aZIMdrrZDCSzpY/75A/xHxQfCgqy6Js/SrwTV0VETMi995w5c27mJl+/
@@ -585,7 +734,7 @@
 9BH88AKd91g+rwmOu3W8ie36f6PPQgarR2iEuBriWojruEEl1kJ00TsCq7CO
 DVqv4FfYrOB8Bw71GM6sAgAA
 """,
-            """
+        """
 androidx/test/AbstractChildObjectComp.class:
 H4sIAAAAAAAA/41SXWvUQBQ9M7ubzaarrfWju1Zrv6TVB9NW3yzCuigEYgS7
 LEifJpuhnW42I8ls6eM++UP8B8WHgoIs+uaPEu/EpSKCmJB77zlzciY5yfcf
@@ -599,7 +748,7 @@
 vERqe8x9Bn97geZHzJ+XBMd6We9io/z/6NOQweIhKgGuB7gR4CZu0YilAC20
 D8EK3MYyrRfwCtwp4PwEJ9evBbwCAAA=
 """,
-            """
+        """
 androidx/test/InterfaceChildClass.class:
 H4sIAAAAAAAA/4VQz28SQRT+ZhZY2FJZqFYKVq21teXg0sabprElMSFBTWrD
 AU4DO9Ipy26yMzQ98rd49mKiMfFgiEf/KOMbSpqYGD3M9973fnxv3vv569t3
@@ -613,7 +762,7 @@
 8FG2pyPPqtGlSXdtqRXYi5LNNj6j9PGvMsXrgqUMx84Ct7BL9iXlblPuTh9O
 G+tt3G2jig1yUWtT/70+mMYm7vfhapQ1HmgUNR5q5DUqGmu/ARpxRv/QAgAA
 """,
-            """
+        """
 androidx/test/InterfaceChildClassComp$Companion.class:
 H4sIAAAAAAAA/5VSTW/TQBB9u07jxARIWz4SvqGp1CJRNxW3IiQIQrKUggRV
 Lj2gjb1tN7HXyLuJesyJH8I/6AmJA4p65EchZp0AV3qZnXlv3sz6rX/++v4D
@@ -628,7 +777,7 @@
 sVEufIBO+VeTB9TbPIIXYTXCWoR13KAUNyOaefsIzKCFNvEGgcEdg+pvYvaF
 YBIDAAA=
 """,
-            """
+        """
 androidx/test/InterfaceChildClassComp.class:
 H4sIAAAAAAAA/41S308TQRD+9lp67XFIWxRLEUUBKVW5gjwJIcEaTZOCCZIm
 wtO2Xcq11z1zu2145G/x2ReihkQTQ3z0jzLOlgoxGsPDzTczN/PNr/3x88s3
@@ -646,7 +795,7 @@
 mHzLzjD1ETOnA0+MRjMF2aCJPA23OuB+jKeEZfLfI8bZA8QquF/BgwrmME8q
 Fip4iMUDMIUClg6QVMgoFBVchUfKmFmFCYX8L78zjKNFBAAA
 """,
-            """
+        """
 androidx/test/InterfaceChildObject.class:
 H4sIAAAAAAAA/4VSy27TQBQ9M0ljxzU0La+EUh59IGCB24odFVKJQLJkgkSj
 SKiriT1tJ3HGkj2JusyKD+EPKhaVQEIR7PgoxB0TihAS2Jr7OHPvuZ4z/vb9
@@ -660,7 +809,7 @@
 G2T9nwVYpAjUeOmi+QZV22fxE/jbc1z+gOWzEuDYLO0dbJX/IsMVIrh6iEqI
 ayGuh9TapBCtEDexeghW0LA12i/gF7hdwP0BVe1yPcgCAAA=
 """,
-            """
+        """
 androidx/test/Outer$InnerClass.class:
 H4sIAAAAAAAA/4VU308cVRT+7sz+mB0WmOVXKay0yorL0nYAW62FVgFFBpel
 QkOs+HLZvcLAMoMzs6S+GJ76JzTRFxNjfOKhTRSMTQy2b/5NxnjuznS3LgSS
@@ -688,7 +837,7 @@
 Gcyug/mYw8fr6PTl84kPvb4mfBg+Mj66fHT7uFU33vZh+siS/h/DktczzQcA
 AA==
 """,
-            """
+        """
 androidx/test/Outer$InnerObject.class:
 H4sIAAAAAAAA/4VUS08TURT+7p0+ptMChSIUUBCpykNpQV1BTJRoHCzFCMEo
 q9t2hKHtjM7cEpas/AkuXLpwxULigkQTg7Dzf/g3jOdOR4vgI2nP+c655zXf
@@ -708,7 +857,7 @@
 4pFjEp3E6o2g3xR9b9RoDBeInuF1aCZGTFw06ekuEcSYiRwur4P5uIKr6zB8
 9Rv3EfORCUCfj3QAkiR/AL5kiZ3FBAAA
 """,
-            """
+        """
 androidx/test/Outer.class:
 H4sIAAAAAAAA/3VRwW7TQBB9u05ixzE0TSlNKE2BFmiKhNuKU6mQSgSSpZBK
 bRQJ5bRJVmUTx5bsTdRjTnwIf1BxqAQSiuDGRyHGbiAHileetzPz5s3u7M9f
@@ -723,7 +872,7 @@
 FZzPWLpMAwZ20nKOB/Rv0DkeEVYJa2mLLewSHpLMMgmXOjA8rHi442EVd2mL
 NQ9lVDpgMe5hvYNsDDvG/Ri5GBsxqr8BxLf8XAEDAAA=
 """,
-            """
+        """
 androidx/test/OuterComp$InnerClassComp$Companion.class:
 H4sIAAAAAAAA/5VTTW8SURQ9d4YyMGKlVC34/YGVGu0AcVdjohgTEmqT2rDp
 wjzgqQ+GN2bmTdMlK3+I/6ArExeGdOmPMt43oI0LE7u599x77rk3cx78+Pnt
@@ -738,7 +887,7 @@
 wFWF2Ru4hduoZegO583s8F08zP4O7AVryodwu1jrotLFOi4zxJUu7944BCWo
 osZ8Aj/BtQT5X3gloppLAwAA
 """,
-            """
+        """
 androidx/test/OuterComp$InnerClassComp.class:
 H4sIAAAAAAAA/41UXVMURxQ9Pfs1Oywwi18IJJq4McuuukA0MYqJijEMATRi
 iGjy0OyOMLDMkJlZyrykfPInWJW8pCoPeeJBKwmkYlWK6Ft+UyqV0zPjomAs
@@ -768,7 +917,7 @@
 KK2L3O9E5WdIGbQFrrCvn9xBysJVC59aGIdFExMWPsMkEwJMYfoOzADdAa4F
 MKI1G6hIMUBPgH0BzkTBswFqAQYi++J/Kz3doBgJAAA=
 """,
-            """
+        """
 androidx/test/OuterComp$InnerObject.class:
 H4sIAAAAAAAA/41US08TURT+7p0+ptMC5SEUUHxQtFClBXVhICZIfAwpxQjB
 KKvbdoSh7QzO3BKWrPQfuHDpwhULiQsSTQzKzp/kwnjuMApCMCbtOd8597zm
@@ -788,7 +937,7 @@
 wKFhkqTikWMM7cTqzaBfnr49ajSGC0TP0Ao0ExdNXDLp6a4QxLCJLEZWwHxc
 xbUVGL765XzEfHQHoNdHOgBJkr8Ar2VkytEEAAA=
 """,
-            """
+        """
 androidx/test/OuterComp.class:
 H4sIAAAAAAAA/31RW2sTQRT+ZjbJbjaxTeMlibX10lqbCm5bfKpFqEVhIaZg
 Q0DyNEmGOslmV3YnoY958of4D4oPBQUJ+uaPEs9so0Wk7rDnO9fvzDnz4+fn
@@ -803,7 +952,7 @@
 voC/PUfxExbPUoeFTZJlCt+jf4Xu8YBwlbCetljDFuEe0SwRcbkDy8d1Hzd8
 3MQtUlHxUUWtA5bgNpY7yCZwE9xJkEuwkmD1F4DlAzIZAwAA
 """,
-            """
+        """
 androidx/test/TestAbstract.class:
 H4sIAAAAAAAA/3VRy04CMRQ9t8AgIwriC/AR3Rh14ahxpzFBExMS1EQNG1eF
 mWgFOsm0EJd8i3/gysSFIS79KOPt6NbNyXnctqft1/f7B4AjrBHqUodJrMLn
@@ -814,7 +963,7 @@
 lVbF2mQ3IfjSf03dGzBWWQWpBnK7b5h6ZSJQY/RScx11xuLvAArw03wlxWWs
 pt9CmOaseI9MEzNNzDZRQpkp5pqoYP4eZLCARc4NfIMlA+8H7YuiztMBAAA=
 """,
-            """
+        """
 androidx/test/TestAbstractComp$Companion.class:
 H4sIAAAAAAAA/5VSTW/TQBB9u07jxARIWz4SPspXkNJK1E3FrQipBCFZSkGC
 Kpce0MZZYBN7jbzrqMec+CH8g56QOKCoR34UYtYJcENwmZ15b96M962///j6
@@ -829,7 +978,7 @@
 bqFT/sDkAfU2T+BFWI+wEWETVyjF1YhmXj8BM2ihTbxBYHDDoPoTIO6Wpv0C
 AAA=
 """,
-            """
+        """
 androidx/test/TestAbstractComp.class:
 H4sIAAAAAAAA/4VRXWsTQRQ9s5vPdWOT+pVYramtMc2D2xRBsEWoEWEhTUFL
 QPI0ScY6yWZWdiahj/kt/oPiQ0FBgo/+KPHuNrYPQn25Z+6dc889c+fX728/
@@ -845,7 +994,7 @@
 J9qPsU34gup3SfFeD7aPso+Kj/tYoyMe+HiI9R6YxiNUe0hpOBobGhmN0h/c
 zA/4OwMAAA==
 """,
-            """
+        """
 androidx/test/TestClass.class:
 H4sIAAAAAAAA/3VRu04CQRQ9d5BFVpQFX+CrVgsXjZ3GRE1MSFATNTRWA7vR
 gWU2YQZCybf4B1YmFoZY+lHGO6utzcl53Jk5N/P1/f4B4BjbhHWpo2Gqoklo
@@ -856,7 +1005,7 @@
 EIL3/evp1messQozDeT33zD/ykSgzuhl5hI2GEu/AyjCz/LNDNexlX0HYYGz
 0iNyTSw2sdREGQFTVJqoYvkRZLCCVc4NfIM1A+8HjoCWJ8sBAAA=
 """,
-            """
+        """
 androidx/test/TestClassComp$Companion.class:
 H4sIAAAAAAAA/5VSTW/TQBB9s07jxARIWz4SyjepaJGom4pbERIEIUVKQYIq
 lx7QJllgE3uNvJuox5z4IfyDnpA4oKhHfhRi1ilwQ3CZnXlv3oz3rb//+PoN
@@ -870,7 +1019,7 @@
 FRFQJ87OnYkf8inOxLWTwkUvuLIEl4IiO48LzAW4y1VUiK7jBpq8wC+8iVbx
 37IH3Fs/QtDFahdrXazjEqe43OWZV49AFg00mbeILK5ZlH8CxwhM7PQCAAA=
 """,
-            """
+        """
 androidx/test/TestClassComp.class:
 H4sIAAAAAAAA/31RXWsTQRQ9s5vPdWOT+pVYq9WmNu2D2xRBMEXQiLCQtqAl
 IHmaJGOdZDMrO7Ohj/kt/oPiQ0FBgo/+KPHuNrYPQl7umXvn3HPP3Pn95/tP
@@ -886,7 +1035,7 @@
 gup3SfFeD7aPqo+aj/tYoyMe+FjHwx6YxiNs9JDRcDQea+Q0Kn8BDq7wpy0D
 AAA=
 """,
-            """
+        """
 androidx/test/TestClassWithArg.class:
 H4sIAAAAAAAA/31QTWsTURQ9781nxsRM4leaaq3aRZtFJy3ulGIMCANRoZZ0
 kdVLZkhfM5mBeS+ly/wW124ERXAhwaU/SrwvDa5EeJx7z32Hcz9+/f7+A8Bz
@@ -900,7 +1049,7 @@
 qjeCjQ3H0zXu4BnFV+shHdwZwYpxN8a9mNo+oBStGFtoj8AUtvFwBE+hrvBI
 IVijqxAqNP4AZEwrjogCAAA=
 """,
-            """
+        """
 androidx/test/TestClassWithArgComp$Companion.class:
 H4sIAAAAAAAA/5VSTW8TMRB99qbZZAmQtnwkfLdNpRZBt6m4FSGVIKRIKUhQ
 hUMPyElM62TXi2wn6jEnfgj/oCckDijqkR+FGG8CHFEv45n35s14n/fnr+8/
@@ -915,7 +1064,7 @@
 Rr7wARr5z0weUG/1GEEby22stLGKG5TiZptm3j4Gs6ihTrxFZHHHovgbuLwQ
 4QkDAAA=
 """,
-            """
+        """
 androidx/test/TestClassWithArgComp.class:
 H4sIAAAAAAAA/41SW08TQRT+ZnvZ7VpkqYgFvCAXLRXZQnwSQoI1xk1KTZDU
 GJ6m7Vi23c6a3WnDI7/FZ1+IGhJNDPHRH2U8s63woAkmu+fMOXPO953L/Pz1
@@ -933,7 +1082,7 @@
 I2nMTbBXUSFdJf8dQrx7iJSHex4WPNzHIh2x5GEZK4dgMR7g4SGsGJMxSjHs
 RGZjODGmYhR/A+R3KVn3AwAA
 """,
-            """
+        """
 androidx/test/TestGraph.class:
 H4sIAAAAAAAA/3VSTW/TQBB9s/mw4waalo8klO/2UDjgtuJGhVQqQJaMkWgU
 qeppE6/aTRwb2Zuox5z4IfyDikMlkFAEN34UYtZEcEB4pTfzZt887Yz84+fn
@@ -947,7 +1096,7 @@
 n7B6URYEHpZ4B5vlb8O7Z4P1E1QCXAtwPcAN3OQU7QAddE9ABW5hg+8LeAVu
 F6j/Ai/P7yRzAgAA
 """,
-            """
+        """
 androidx/test/TestInterface.class:
 H4sIAAAAAAAA/32Oz0rDQBDGv9lo08Z/qVqoiK9g2tKbJy9CoCKoeMlpm2xl
 m3QD2Wnpsc/lQXr2ocSJ3p2Bb76Zgd/M1/fHJ4ApBoRr7YqmtsU2YeM5eRVJ
@@ -956,7 +1105,7 @@
 L/W6yc2DrQzh6nnt2K7Mm/V2Xpl752rWbGvnO8LGAf5C4eJXz3EpdSy8Q8lO
 hiBFmKKboodILI5SHOMkA3mc4iyD8og9+j/Vk+x/PAEAAA==
 """,
-            """
+        """
 androidx/test/TestKt.class:
 H4sIAAAAAAAA/4VUbU/TUBR+bjvWrgzW8b6BiLzo5gsFfBc0ISQmjRMSJBhC
 YtJt11kYbdJ7R/jIb/EXqHwgkcQQP/qjjOc2xGkLug/3nvvc53l6zunpfvz8
@@ -975,7 +1124,7 @@
 36KmVHahu6i6uO3iDu66uId5Fw4WdsFU+5d2kRcwBXICPQJZgX6BglBgn8CQ
 wLDAgMDgL1Y9gwBpBQAA
 """,
-            """
+        """
 androidx/test/TestObject.class:
 H4sIAAAAAAAA/3VSTWvbQBB9s7ZlWXEbN/2InfQrH4ekhyoJvTUUktCCQFWh
 MYaQ09pa0rVlCaS1ydGn/pD+g9BDoIVi2lt/VOmscNpDqRbezHs789gZ9PPX
@@ -989,5 +1138,4 @@
 KgWBzRIfYav8cXj3bLByjkqAuwHuBbiPB5xiNUAbnXNQgTWs830Br8DDAs5v
 jU1b0HUCAAA=
 """
-        )
-        .bytecode
+    )
diff --git a/navigation/navigation-runtime-lint/build.gradle b/navigation/navigation-runtime-lint/build.gradle
index f50d052..f7c323c 100644
--- a/navigation/navigation-runtime-lint/build.gradle
+++ b/navigation/navigation-runtime-lint/build.gradle
@@ -38,8 +38,8 @@
     bundleInside(project(":navigation:navigation-lint-common"))
 
     testImplementation(libs.kotlinStdlib)
-    testImplementation(libs.androidLint)
-    testImplementation(libs.androidLintTests)
+    testImplementation(libs.androidLintPrev)
+    testImplementation(libs.androidLintPrevTests)
     testImplementation(libs.junit)
     testImplementation(libs.truth)
 }
diff --git a/navigation/navigation-runtime-lint/src/main/java/androidx/navigation/runtime/lint/NavigationRuntimeIssueRegistry.kt b/navigation/navigation-runtime-lint/src/main/java/androidx/navigation/runtime/lint/NavigationRuntimeIssueRegistry.kt
index bc11285..c6a3e72 100644
--- a/navigation/navigation-runtime-lint/src/main/java/androidx/navigation/runtime/lint/NavigationRuntimeIssueRegistry.kt
+++ b/navigation/navigation-runtime-lint/src/main/java/androidx/navigation/runtime/lint/NavigationRuntimeIssueRegistry.kt
@@ -34,6 +34,8 @@
                 DeepLinkInActivityDestinationDetector.DeepLinkInActivityDestination,
                 WrongStartDestinationTypeDetector.WrongStartDestinationType,
                 WrongNavigateRouteDetector.WrongNavigateRouteType,
+                TypeSafeDestinationMissingAnnotationDetector.MissingKeepAnnotationIssue,
+                TypeSafeDestinationMissingAnnotationDetector.MissingSerializableAnnotationIssue,
             )
 
     override val vendor =
diff --git a/navigation/navigation-runtime-lint/src/main/java/androidx/navigation/runtime/lint/TypeSafeDestinationMissingAnnotationDetector.kt b/navigation/navigation-runtime-lint/src/main/java/androidx/navigation/runtime/lint/TypeSafeDestinationMissingAnnotationDetector.kt
new file mode 100644
index 0000000..53b573f
--- /dev/null
+++ b/navigation/navigation-runtime-lint/src/main/java/androidx/navigation/runtime/lint/TypeSafeDestinationMissingAnnotationDetector.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.navigation.runtime.lint
+
+import androidx.navigation.lint.common.BaseTypeSafeDestinationMissingAnnotationDetector
+import androidx.navigation.lint.common.createMissingKeepAnnotationIssue
+import androidx.navigation.lint.common.createMissingSerializableAnnotationIssue
+import com.android.tools.lint.detector.api.Issue
+
+/**
+ * Checks for missing annotations on type-safe route declarations
+ *
+ * Retrieves route classes/objects by tracing KClasses passed as route during NavDestination
+ * creation
+ */
+class TypeSafeDestinationMissingAnnotationDetector :
+    BaseTypeSafeDestinationMissingAnnotationDetector(
+        methodNames = listOf("activity"),
+        constructorNames = listOf("androidx.navigation.ActivityNavigatorDestinationBuilder")
+    ) {
+
+    companion object {
+        val MissingSerializableAnnotationIssue =
+            createMissingSerializableAnnotationIssue(
+                TypeSafeDestinationMissingAnnotationDetector::class.java
+            )
+        val MissingKeepAnnotationIssue =
+            createMissingKeepAnnotationIssue(
+                TypeSafeDestinationMissingAnnotationDetector::class.java
+            )
+    }
+
+    override fun getMissingSerializableIssue(): Issue = MissingSerializableAnnotationIssue
+
+    override fun getMissingKeepIssue(): Issue = MissingKeepAnnotationIssue
+}
diff --git a/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/MissingKeepAnnotationDetectorTest.kt b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/MissingKeepAnnotationDetectorTest.kt
new file mode 100644
index 0000000..72bf33d
--- /dev/null
+++ b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/MissingKeepAnnotationDetectorTest.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.navigation.runtime.lint
+
+import androidx.navigation.lint.common.KEEP_ANNOTATION
+import androidx.navigation.lint.common.NAVIGATION_STUBS
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class MissingKeepAnnotationDetectorTest : LintDetectorTest() {
+    override fun getDetector(): Detector = TypeSafeDestinationMissingAnnotationDetector()
+
+    override fun getIssues(): List<Issue> =
+        listOf(TypeSafeDestinationMissingAnnotationDetector.MissingKeepAnnotationIssue)
+
+    @Test
+    fun testActivityNavigatorDestinationBuilderConstructor_noError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.annotation.Keep
+
+                @Keep enum class TestEnum { ONE, TWO }
+                class TestClass(val arg: TestEnum)
+
+                fun navigation() {
+                    ActivityNavigatorDestinationBuilder(route = TestClass::class)
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testActivityNavigatorDestinationBuilderConstructor_hasError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+
+                enum class TestEnum { ONE, TWO }
+                class TestClass(val arg: TestEnum)
+
+                fun navigation() {
+                    ActivityNavigatorDestinationBuilder(route = TestClass::class)
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+            )
+            .run()
+            .expect(
+                """
+src/com/example/TestEnum.kt:5: Warning: To prevent this Enum's serializer from being obfuscated in minified builds, annotate it with @androidx.annotation.Keep [MissingKeepAnnotation]
+enum class TestEnum { ONE, TWO }
+           ~~~~~~~~
+0 errors, 1 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun testActivity_noError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.annotation.Keep
+
+                class RouteClass
+                @Keep enum class TestEnum { ONE, TWO }
+                class TestClass(val arg: TestEnum)
+
+                fun navigation() {
+                    val builder = NavGraphBuilder(RouteClass::class)
+                    builder.activity<TestClass>()
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testActivity_hasError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+
+                class RouteClass
+                enum class TestEnum { ONE, TWO }
+                class TestClass(val arg: TestEnum)
+
+                fun navigation() {
+                    val builder = NavGraphBuilder(RouteClass::class)
+                    builder.activity<TestClass>()
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+            )
+            .run()
+            .expect(
+                """
+src/com/example/RouteClass.kt:6: Warning: To prevent this Enum's serializer from being obfuscated in minified builds, annotate it with @androidx.annotation.Keep [MissingKeepAnnotation]
+enum class TestEnum { ONE, TWO }
+           ~~~~~~~~
+0 errors, 1 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
+    val STUBS = arrayOf(*NAVIGATION_STUBS, ACTIVITY_NAVIGATION_DESTINATION_BUILDER, KEEP_ANNOTATION)
+}
diff --git a/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/MissingSerializableAnnotationDetectorTest.kt b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/MissingSerializableAnnotationDetectorTest.kt
new file mode 100644
index 0000000..10f20eb
--- /dev/null
+++ b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/MissingSerializableAnnotationDetectorTest.kt
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.navigation.runtime.lint
+
+import androidx.navigation.lint.common.NAVIGATION_STUBS
+import androidx.navigation.lint.common.SERIALIZABLE_ANNOTATION
+import androidx.navigation.lint.common.SERIALIZABLE_TEST_CLASS
+import androidx.navigation.lint.common.TEST_CLASS
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class MissingSerializableAnnotationDetectorTest : LintDetectorTest() {
+    override fun getDetector(): Detector = TypeSafeDestinationMissingAnnotationDetector()
+
+    override fun getIssues(): List<Issue> =
+        listOf(TypeSafeDestinationMissingAnnotationDetector.MissingSerializableAnnotationIssue)
+
+    @Test
+    fun testActivityNavigatorDestinationBuilderConstructor_noError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.testSerializable.*
+
+                fun navigation() {
+                    ActivityNavigatorDestinationBuilder(route = TestClass::class)
+                    ActivityNavigatorDestinationBuilder(route = TestObject::class)
+                    ActivityNavigatorDestinationBuilder(route = TestDataObject::class)
+                    ActivityNavigatorDestinationBuilder(route = Outer::class)
+                    ActivityNavigatorDestinationBuilder(route = Outer.InnerObject::class)
+                    ActivityNavigatorDestinationBuilder(route = Outer.InnerClass::class)
+                    ActivityNavigatorDestinationBuilder(route = TestInterface::class)
+                    ActivityNavigatorDestinationBuilder(route = InterfaceChildClass::class)
+                    ActivityNavigatorDestinationBuilder(route = InterfaceChildObject::class)
+                    ActivityNavigatorDestinationBuilder(route = TestAbstract::class)
+                    ActivityNavigatorDestinationBuilder(route = AbstractChildClass::class)
+                    ActivityNavigatorDestinationBuilder(route = AbstractChildObject::class)
+                    ActivityNavigatorDestinationBuilder(route = SealedClass::class)
+                    ActivityNavigatorDestinationBuilder(route = SealedClass.SealedSubClass::class)
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+                SERIALIZABLE_TEST_CLASS.kotlin
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testActivityNavigatorDestinationBuilderConstructor_hasError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.test.*
+
+                fun navigation() {
+                    ActivityNavigatorDestinationBuilder(route = TestClass::class)
+                    ActivityNavigatorDestinationBuilder(route = TestObject::class)
+                    ActivityNavigatorDestinationBuilder(route = TestDataObject::class)
+                    ActivityNavigatorDestinationBuilder(route = Outer::class)
+                    ActivityNavigatorDestinationBuilder(route = Outer.InnerObject::class)
+                    ActivityNavigatorDestinationBuilder(route = Outer.InnerClass::class)
+                    ActivityNavigatorDestinationBuilder(route = TestInterface::class)
+                    ActivityNavigatorDestinationBuilder(route = InterfaceChildClass::class)
+                    ActivityNavigatorDestinationBuilder(route = InterfaceChildObject::class)
+                    ActivityNavigatorDestinationBuilder(route = TestAbstract::class)
+                    ActivityNavigatorDestinationBuilder(route = AbstractChildClass::class)
+                    ActivityNavigatorDestinationBuilder(route = AbstractChildObject::class)
+                    ActivityNavigatorDestinationBuilder(route = SealedClass::class)
+                    ActivityNavigatorDestinationBuilder(route = SealedClass.SealedSubClass::class)
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+                TEST_CLASS.kotlin
+            )
+            .run()
+            .expect(
+                """
+src/androidx/test/Test.kt:11: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object TestObject
+       ~~~~~~~~~~
+src/androidx/test/Test.kt:13: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+data object TestDataObject
+            ~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:15: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+class TestClass
+      ~~~~~~~~~
+src/androidx/test/Test.kt:19: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object Outer {
+       ~~~~~
+src/androidx/test/Test.kt:20: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+    data object InnerObject
+                ~~~~~~~~~~~
+src/androidx/test/Test.kt:22: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+    data class InnerClass (
+               ~~~~~~~~~~
+src/androidx/test/Test.kt:29: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+class InterfaceChildClass(val arg: Boolean): TestInterface
+      ~~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:30: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object InterfaceChildObject: TestInterface
+       ~~~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:32: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+abstract class TestAbstract
+               ~~~~~~~~~~~~
+src/androidx/test/Test.kt:33: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+class AbstractChildClass(val arg: Boolean): TestAbstract()
+      ~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:34: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object AbstractChildObject: TestAbstract()
+       ~~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:36: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+sealed class SealedClass {
+             ~~~~~~~~~~~
+src/androidx/test/Test.kt:37: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+    class SealedSubClass : SealedClass()
+          ~~~~~~~~~~~~~~
+13 errors, 0 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
+    @Test
+    fun testActivity_noError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.testSerializable.*
+
+                @Serializable class RouteClass
+
+                fun navigation() {
+                    val builder = NavGraphBuilder(route = RouteClass::class)
+
+                    builder.activity<TestClass>()
+                    builder.activity<TestObject>()
+                    builder.activity<TestDataObject>()
+                    builder.activity<Outer>()
+                    builder.activity<Outer.InnerObject>()
+                    builder.activity<Outer.InnerClass>()
+                    builder.activity<TestInterface>()
+                    builder.activity<InterfaceChildClass>()
+                    builder.activity<InterfaceChildObject>()
+                    builder.activity<TestAbstract>()
+                    builder.activity<AbstractChildClass>()
+                    builder.activity<AbstractChildObject>()
+                    builder.activity<SealedClass>()
+                    builder.activity<SealedClass.SealedSubClass>()
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+                TEST_CLASS.kotlin
+            )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testActivity_hasError() {
+        lint()
+            .files(
+                kotlin(
+                        """
+                package com.example
+
+                import androidx.navigation.*
+                import androidx.test.*
+
+                @Serializable class RouteClass
+
+                fun navigation() {
+                    val builder = NavGraphBuilder(route = RouteClass::class)
+
+                    builder.activity<TestClass>()
+                    builder.activity<TestObject>()
+                    builder.activity<TestDataObject>()
+                    builder.activity<Outer>()
+                    builder.activity<Outer.InnerObject>()
+                    builder.activity<Outer.InnerClass>()
+                    builder.activity<TestInterface>()
+                    builder.activity<InterfaceChildClass>()
+                    builder.activity<InterfaceChildObject>()
+                    builder.activity<TestAbstract>()
+                    builder.activity<AbstractChildClass>()
+                    builder.activity<AbstractChildObject>()
+                    builder.activity<SealedClass>()
+                    builder.activity<SealedClass.SealedSubClass>()
+                }
+                """
+                    )
+                    .indented(),
+                *STUBS,
+                TEST_CLASS.kotlin
+            )
+            .run()
+            .expect(
+                """
+src/androidx/test/Test.kt:11: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object TestObject
+       ~~~~~~~~~~
+src/androidx/test/Test.kt:13: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+data object TestDataObject
+            ~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:15: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+class TestClass
+      ~~~~~~~~~
+src/androidx/test/Test.kt:19: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object Outer {
+       ~~~~~
+src/androidx/test/Test.kt:20: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+    data object InnerObject
+                ~~~~~~~~~~~
+src/androidx/test/Test.kt:22: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+    data class InnerClass (
+               ~~~~~~~~~~
+src/androidx/test/Test.kt:29: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+class InterfaceChildClass(val arg: Boolean): TestInterface
+      ~~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:30: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object InterfaceChildObject: TestInterface
+       ~~~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:32: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+abstract class TestAbstract
+               ~~~~~~~~~~~~
+src/androidx/test/Test.kt:33: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+class AbstractChildClass(val arg: Boolean): TestAbstract()
+      ~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:34: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+object AbstractChildObject: TestAbstract()
+       ~~~~~~~~~~~~~~~~~~~
+src/androidx/test/Test.kt:36: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+sealed class SealedClass {
+             ~~~~~~~~~~~
+src/androidx/test/Test.kt:37: Error: To use this class or object as a type-safe destination, annotate it with @Serializable [MissingSerializableAnnotation]
+    class SealedSubClass : SealedClass()
+          ~~~~~~~~~~~~~~
+13 errors, 0 warnings
+            """
+                    .trimIndent()
+            )
+    }
+
+    val STUBS =
+        arrayOf(*NAVIGATION_STUBS, ACTIVITY_NAVIGATION_DESTINATION_BUILDER, SERIALIZABLE_ANNOTATION)
+}
diff --git a/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/Stubs.kt b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/Stubs.kt
new file mode 100644
index 0000000..a4e08a3
--- /dev/null
+++ b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/Stubs.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.navigation.runtime.lint
+
+import androidx.navigation.lint.common.bytecodeStub
+
+internal val ACTIVITY_NAVIGATION_DESTINATION_BUILDER =
+    bytecodeStub(
+        "ActivityNavigatorDestinationBuilder.kt",
+        "androidx/navigation",
+        0x2a71d14,
+        """
+package androidx.navigation
+
+import kotlin.reflect.KClass
+
+public abstract class Navigator<D : NavDestination>
+
+public open class ActivityNavigator : Navigator<ActivityNavigator.Destination>() {
+    public open class Destination: NavDestination()
+}
+
+public class ActivityNavigatorDestinationBuilder :
+    NavDestinationBuilder<ActivityNavigator.Destination> {
+        public constructor(route: KClass<out Any>)
+    }
+
+public inline fun <reified T : Any> NavGraphBuilder.activity() {}
+            """
+            .trimIndent(),
+        """
+                META-INF/main.kotlin_module:
+                H4sIAAAAAAAA/y2KQQrCQAxFI4LQuBBGvIDgxkXvUHUhFFx5gaETamCalGks
+                7e0dqx8+fN77ALAGgFVuAf9gwL2XkJTDVIofufXGKu5UNcYj2/z4MU03Goxl
+                sZc3x0CpNoeVzPZiafPe5etVxZLGuMgig7sOVhsecNtoV9Lkuz6S2zzpi49w
+                hg8VwfQ8lQAAAA==
+                """,
+        """
+                androidx/navigation/ActivityNavigator$Destination.class:
+                H4sIAAAAAAAA/5VSTU8UQRB91cPOLsMqHwouICIGiHpwAb1pSABDMsnCAche
+                ODU7Ha3sbE8y3bvB2/4W/4EnEw5kw9EfZawZOEDggId+XfXqVeqj+8/fi0sA
+                n7BC2NQ2yTNOzptWD/ib9pzZ5k7H84D9j8NrKstXvxrn2ZbRKojw5qE0kd/R
+                BYTwC1v224Tg7bt2HRWEEcZQJYz57+wIH1v/Xf8zYbrVzXzKtnlgvE6018Kp
+                3iCQoaiA8QJAoK7w51x4G2Ilm4Tl0TCKVEOVZzSszTVGwy21QbuVq5+hmlKF
+                bIuw+mBfdweUomuP6r6KF4SJW5mE9XuiW+HdPqeJyT90vexpL0sMYbLF1hz2
+                e2cmP9FnqTAzrayj07bOufBvyHpsrcn3Uu2cke1Gx1k/75h9LmLzR33ruWfa
+                7FjEO9ZmviznsAIlr3Kzs+KRBBfFa5Y+UHn/G7VfYii8FAxLsoYlwfq1AOOI
+                5A7wSjASTqGBeWGXy6wFvC4/nKxBtPVTBDGexHgaYxJTYmI6xgyenYIcnmNW
+                4g6Rw5xD+A+jXaqlrQIAAA==
+                """,
+        """
+                androidx/navigation/ActivityNavigator.class:
+                H4sIAAAAAAAA/51RTW/TQBB9Yztx4gSaBigJUEo/+JQg6ceprSq1RUiWQg+0
+                yiWnbbxqV3HWkncdlVt/C/+AExIHVHHkRyHGbgWVWlXAYd/OvHnzRpr58fPr
+                NwBrWCI8FTpKExWddLSYqCNhVaI720OrJsp+3DunktQHEQ5612l/azavLV+x
+                WnorjVW6qG5sbRBmb3T14RHKm0oru0VwX7zs11GGH6CECsGzx8oQnv/daB42
+                3RslNla6815aEQkrmHPGE5fXQTlUcwCBRsyfqDzrchQtE16fnTYCp+X8eRWn
+                MtM6O12pNL2m03W6tE7eTun7p7LTcPOmFcLczTvj6av/sTZC7VJKWP5nCx8P
+                CM+uSC4pdjIVRzJ9M7K85t0kkoSpntJyLxsfyvRAHMbMNHvJUMR9kao8vyDr
+                odYy3Y2FMZKPU91XR2yZpVwK9pMsHcp3Kte1P2TaqrHsK6O4cVvrxBajDebh
+                8IEvTpHfm3GWs06RA6VXX1D9zIGDx4zlgvQxx1g/FyBAjX8PTxgD5h6yto0q
+                W+ddLhaK/xEW+V/nep17bg3ghrgdYipEA9McohniDu4OQAb3MDNAyaBmcN+g
+                ZeAbtH8BM+ZISEwDAAA=
+                """,
+        """
+                androidx/navigation/ActivityNavigatorDestinationBuilder.class:
+                H4sIAAAAAAAA/51TW08TQRT+Zlu6ZbmVKhRQ8cLFQpUtl8QYCAqoSWOtRgwv
+                vDhtxzLtdjbZmTb4xm/xH/ik8cEQH/1RxrPbik0KSfVhZ77znfOdc2bm7M9f
+                374D2MIWwyOuqoEvq6eu4m1Z40b6yt2rGNmW5mOpQ/nBM6GNVJFzvyW9qghs
+                MIb3xcvUpOqP37k0tK/QYo9ye3ebYWXgCjbiDIkdqaTZZZjLFhu+8aRyA/HB
+                ExXjvjzwuNbbK0cM7hXOnVyxztvc9biqua/LdfJQE6FioegHNbcuTDngUmmX
+                K+WbqLp2S74ptTyPmh0K/JYRSTgM890C9XbTlcqIQHHPLSgTkFpWtI1RhqnK
+                iag0uvI3POBNQYEM97P9bfQwh2GSGrU1inFMOBhDiiGWDe0E0g6GcI0hbk6k
+                Zng82LX33yadJnPFDTJM/vG8EoZXueHEWc12jIaKhctwuICBNYg/laGVJ1Rd
+                Z3h+fjbjWDPWxXd+1oEpAuQ7P9tIpuNpK2/l2X4mOZ0eT8XmnHQiyYgbysd/
+                fEpYqUSYbIMhN/j8UYeb/zGCLDzG+j/rbCwzLA0ks5FlGOnRMiwP8D5rDUOP
+                fOBXBcNEUSpRajXLInjHyx4x6aJf4d4RD2Rod8nhQ1mjHK2A8OLbljKyKQqq
+                LbUk98X47f2dbIbRglIiiJ5dkOkc+q2gIl7IMN1sN8VRJ0GPDuuwaAq7M0BD
+                aSOGB2Q9Id6ifSSXHvmKydUvuP6ZTAsPaU1EoWNYIzzdCcMUIUTIQYb8bhSd
+                RJ52O8w9TCBO9UAB9ONgBavEdcJi2Ij2HDZpf0r+Gepq9hixAuYKuFHATdwi
+                iPkCbuPOMZjGXdw7hq0xrbGgsajhaGQ0ljTs3xTF98w2BQAA
+                """,
+        """
+                androidx/navigation/ActivityNavigatorDestinationBuilderKt.class:
+                H4sIAAAAAAAA/41STW/TQBB966SJY/qRprQkAQqUtE17wCnqhTZEKiDAagiI
+                RLnktHFMsomzRvY6Kree+D/cEAdUceRHIWbdSFTAoZJ35s2b8ZuZtX/++vYd
+                wCH2GJ5wOQgDMTizJZ+JIVcikPaJq8RMqE+tSyoIX3iREjJJPouFP/DCU5UF
+                Y8iP+YzbPpdD+21/7LnEphhMPhdgqFab/+tAyq9C/nE0Vzve6zI06p2j5t96
+                x43rC2TqaiSihgmTYXMSKF9Iezyb2kIqL5Tctx2pQiEj4UZZWAzr7shzJ61A
+                tWLff8dDPvWokGG3+u8YV5i2FhlSx0UsYsnCDSwzLFVE5UPlz97MYViu6Hmu
+                kNvXWoVhtTmf/o2n+IArTpwxnaXoozFtctqAmkw0MCh5JjSqERocMBxenOet
+                i3PLyBuXTpuiUS4RKBs1tmWZVGEU2b5RS9FJv/7x2dTvPmaJbIdh5xr/wKOJ
+                Ykg/DwYew0pTSK8VT/te2OF9n5hCM3C53+Wh0PGczLXFkDTikLDVDuLQ9V4K
+                nSi9j6USU68rIkGVJ1IGKukV4QAG0kg2zpewgAzFDyl6St7Qt7FfyH3FSqr+
+                RV8GKmQztIWJPLYJbxBnUpzHKlkqRwFr5HeS6iyd3QRtoUr+iGpuUpP1HlIO
+                NhzcclBEyUEZtx3cwd0eWIRN3OthIdLP/QgPErv2G3NDy2lXAwAA
+                """,
+        """
+                androidx/navigation/Navigator.class:
+                H4sIAAAAAAAA/41QXWsTQRQ9M5vPbbTb1o9UrW1BiuahG4NPaSm0BiGwVrCS
+                lzxNsku8zWYWdmZDfctv8R/4JPggwUd/lHh3E0SEgi/nnnPmzL0z9+evb98B
+                vMKhwJ7SYZpQeONrNaeJspRo/3JFk7QKIdA97XWDW2K9yFjShTw5C67VXPmx
+                0hP/3eg6GtsTAe9fr4qSQOWUNNkzAef5i0EDFVRdlFETKNmPZAT2b5u3ehb3
+                3QqmiY1J+28jq0JlFXtyNnf4XyKHeg4QEFP2byhXbWbhS4HOcuG5sind5aIo
+                0mOyXNSOmstFq1bjU9GSbdmRbeei/ONzRXql/GaHm/UEnv3PKkQ++uh8bGlO
+                9tOfd/8VucgoDqP0eGr506+TMBLYDEhHl9lsFKUf1ChmZztIxioeqJRyvTbr
+                VzThHlnK3L1KsnQcvaH8YPd9pi3NogEZ4uS51oktZpnSISTvd72RfN2Mj1n5
+                hQbKra+of2Ei8YSxsjKxx9hYcxcbXB08LVIO9ov6CAdcu5xpcObOEE4fd/vY
+                7MPDFlNs97GDe0MIg/t4METZYMPgoUHTYNeg+hsUN/EQjAIAAA==
+                """
+    )
diff --git a/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/WrongNavigateRouteDetectorTest.kt b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/WrongNavigateRouteDetectorTest.kt
index e175228..ff6c021 100644
--- a/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/WrongNavigateRouteDetectorTest.kt
+++ b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/WrongNavigateRouteDetectorTest.kt
@@ -17,7 +17,7 @@
 package androidx.navigation.runtime.lint
 
 import androidx.navigation.lint.common.NAVIGATION_STUBS
-import androidx.navigation.lint.common.TEST_CODE
+import androidx.navigation.lint.common.TEST_CLASS
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.checks.infrastructure.TestMode
 import com.android.tools.lint.detector.api.Detector
@@ -54,7 +54,7 @@
                     )
                     .indented(),
                 *NAVIGATION_STUBS,
-                TEST_CODE
+                TEST_CLASS.bytecode
             )
             .skipTestModes(TestMode.FULLY_QUALIFIED)
             .run()
@@ -97,7 +97,7 @@
                     )
                     .indented(),
                 *NAVIGATION_STUBS,
-                TEST_CODE
+                TEST_CLASS.bytecode
             )
             .skipTestModes(TestMode.FULLY_QUALIFIED)
             .run()
@@ -151,7 +151,7 @@
                     )
                     .indented(),
                 *NAVIGATION_STUBS,
-                TEST_CODE
+                TEST_CLASS.bytecode
             )
             .skipTestModes(TestMode.FULLY_QUALIFIED)
             .run()
diff --git a/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/WrongPopBackStackRouteDetectorTest.kt b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/WrongPopBackStackRouteDetectorTest.kt
index 80b9358..939dd4ec 100644
--- a/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/WrongPopBackStackRouteDetectorTest.kt
+++ b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/WrongPopBackStackRouteDetectorTest.kt
@@ -17,7 +17,7 @@
 package androidx.navigation.runtime.lint
 
 import androidx.navigation.lint.common.NAVIGATION_STUBS
-import androidx.navigation.lint.common.TEST_CODE
+import androidx.navigation.lint.common.TEST_CLASS
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.checks.infrastructure.TestMode
 import com.android.tools.lint.detector.api.Detector
@@ -54,7 +54,7 @@
                     )
                     .indented(),
                 *NAVIGATION_STUBS,
-                TEST_CODE
+                TEST_CLASS.bytecode
             )
             .skipTestModes(TestMode.FULLY_QUALIFIED)
             .run()
@@ -97,7 +97,7 @@
                     )
                     .indented(),
                 *NAVIGATION_STUBS,
-                TEST_CODE
+                TEST_CLASS.bytecode
             )
             .skipTestModes(TestMode.FULLY_QUALIFIED)
             .run()
@@ -137,7 +137,7 @@
                     )
                     .indented(),
                 *NAVIGATION_STUBS,
-                TEST_CODE
+                TEST_CLASS.bytecode
             )
             .skipTestModes(TestMode.FULLY_QUALIFIED)
             .run()
diff --git a/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/WrongStartDestinationTypeDetectorTest.kt b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/WrongStartDestinationTypeDetectorTest.kt
index af29da5..5c2503e 100644
--- a/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/WrongStartDestinationTypeDetectorTest.kt
+++ b/navigation/navigation-runtime-lint/src/test/java/androidx/navigation/runtime/lint/WrongStartDestinationTypeDetectorTest.kt
@@ -17,7 +17,7 @@
 package androidx.navigation.runtime.lint
 
 import androidx.navigation.lint.common.NAVIGATION_STUBS
-import androidx.navigation.lint.common.TEST_CODE
+import androidx.navigation.lint.common.TEST_CLASS
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.checks.infrastructure.TestMode
 import com.android.tools.lint.detector.api.Detector
@@ -55,7 +55,7 @@
                     )
                     .indented(),
                 *NAVIGATION_STUBS,
-                TEST_CODE
+                TEST_CLASS.bytecode
             )
             .skipTestModes(TestMode.FULLY_QUALIFIED)
             .run()
@@ -95,7 +95,7 @@
                     )
                     .indented(),
                 *NAVIGATION_STUBS,
-                TEST_CODE
+                TEST_CLASS.bytecode
             )
             .run()
             .expectClean()
@@ -129,7 +129,7 @@
                     )
                     .indented(),
                 *NAVIGATION_STUBS,
-                TEST_CODE
+                TEST_CLASS.bytecode
             )
             .run()
             .expectClean()
@@ -168,7 +168,7 @@
                     )
                     .indented(),
                 *NAVIGATION_STUBS,
-                TEST_CODE
+                TEST_CLASS.bytecode
             )
             .run()
             .expect(
@@ -289,7 +289,7 @@
                     )
                     .indented(),
                 *NAVIGATION_STUBS,
-                TEST_CODE
+                TEST_CLASS.bytecode
             )
             .run()
             .expectClean()
@@ -324,7 +324,7 @@
                     )
                     .indented(),
                 *NAVIGATION_STUBS,
-                TEST_CODE
+                TEST_CLASS.bytecode
             )
             .run()
             .expectClean()
@@ -362,7 +362,7 @@
                     )
                     .indented(),
                 *NAVIGATION_STUBS,
-                TEST_CODE
+                TEST_CLASS.bytecode
             )
             .run()
             .expect(
diff --git a/navigation/navigation-ui-ktx/build.gradle b/navigation/navigation-ui-ktx/build.gradle
index 77f8cdf..27b2f04 100644
--- a/navigation/navigation-ui-ktx/build.gradle
+++ b/navigation/navigation-ui-ktx/build.gradle
@@ -31,9 +31,6 @@
 
 dependencies {
     api(project(":navigation:navigation-ui"))
-    api(project(":navigation:navigation-runtime-ktx")) {
-        because 'Mirror navigation-ui dependency graph for -ktx artifacts'
-    }
 }
 
 androidx {
diff --git a/navigation/navigation-ui/build.gradle b/navigation/navigation-ui/build.gradle
index ab3978b..7b15eb9 100644
--- a/navigation/navigation-ui/build.gradle
+++ b/navigation/navigation-ui/build.gradle
@@ -37,19 +37,25 @@
 }
 
 dependencies {
+    api(project(":navigation:navigation-common"))
     api(project(":navigation:navigation-runtime"))
     api("androidx.customview:customview:1.1.0")
     api("androidx.drawerlayout:drawerlayout:1.1.1")
     api("com.google.android.material:material:1.4.0")
+
+    implementation("androidx.appcompat:appcompat:1.2.0")
+    implementation("androidx.core:core-ktx:1.2.0")
+    implementation("androidx.coordinatorlayout:coordinatorlayout:1.1.0")
     implementation("androidx.transition:transition:1.3.0")
-    api("androidx.annotation:annotation-experimental:1.4.1")
 
     androidTestImplementation(project(":internal-testutils-navigation"), {
         exclude group: "androidx.navigation", module: "navigation-common"
     })
+    androidTestImplementation(libs.junit)
     androidTestImplementation(libs.kotlinStdlib)
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testCore)
+    androidTestImplementation(libs.testMonitor)
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(libs.testRules)
     androidTestImplementation(libs.truth)
diff --git a/playground-common/gradle/wrapper/gradle-wrapper.properties b/playground-common/gradle/wrapper/gradle-wrapper.properties
index c8a852e..7859569 100644
--- a/playground-common/gradle/wrapper/gradle-wrapper.properties
+++ b/playground-common/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
-distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
+distributionSha256Sum=31c55713e40233a8303827ceb42ca48a47267a0ad4bab9177123121e71524c26
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/InterfaceParser.kt b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/InterfaceParser.kt
index 25f5c7b..a659c89 100644
--- a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/InterfaceParser.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/InterfaceParser.kt
@@ -28,6 +28,7 @@
 import com.google.devtools.ksp.symbol.ClassKind
 import com.google.devtools.ksp.symbol.KSClassDeclaration
 import com.google.devtools.ksp.symbol.KSFunctionDeclaration
+import com.google.devtools.ksp.symbol.KSPropertyDeclaration
 import com.google.devtools.ksp.symbol.KSValueParameter
 import com.google.devtools.ksp.symbol.Modifier
 
@@ -55,13 +56,6 @@
         if (
             interfaceDeclaration.declarations
                 .filterIsInstance<KSClassDeclaration>()
-                .any(KSClassDeclaration::isCompanionObject)
-        ) {
-            logger.error("Error in $name: annotated interfaces cannot declare companion objects.")
-        }
-        if (
-            interfaceDeclaration.declarations
-                .filterIsInstance<KSClassDeclaration>()
                 .filter {
                     listOf(
                             ClassKind.OBJECT,
@@ -75,6 +69,12 @@
         ) {
             logger.error("Error in $name: annotated interfaces cannot declare objects or classes.")
         }
+
+        interfaceDeclaration.declarations
+            .filterIsInstance<KSClassDeclaration>()
+            .filter { it.isCompanionObject }
+            .forEach { validateCompanion(name, it) }
+
         val invalidModifiers =
             interfaceDeclaration.modifiers.filterNot(validInterfaceModifiers::contains)
         if (invalidModifiers.isNotEmpty()) {
@@ -114,6 +114,41 @@
         )
     }
 
+    private fun validateCompanion(name: String, companion: KSClassDeclaration) {
+        val nonConstValues =
+            companion.declarations
+                .filterIsInstance<KSPropertyDeclaration>()
+                .filter { !it.modifiers.contains(Modifier.CONST) }
+                .toList()
+        if (nonConstValues.isNotEmpty()) {
+            logger.error(
+                "Error in $name: companion object cannot declare non-const values (${
+                    nonConstValues.joinToString(limit = 3) { it.simpleName.getShortName() }
+                })."
+            )
+        }
+        val methods =
+            companion.declarations
+                .filterIsInstance<KSFunctionDeclaration>()
+                .filter { it.simpleName.getFullName() != "<init>" }
+                .toList()
+        if (methods.isNotEmpty()) {
+            logger.error(
+                "Error in $name: companion object cannot declare methods (${
+                    methods.joinToString(limit = 3) { it.simpleName.getShortName() }
+                })."
+            )
+        }
+        val classes = companion.declarations.filterIsInstance<KSClassDeclaration>().toList()
+        if (classes.isNotEmpty()) {
+            logger.error(
+                "Error in $name: companion object cannot declare classes (${
+                    classes.joinToString(limit = 3) { it.simpleName.getShortName() }
+                })."
+            )
+        }
+    }
+
     private fun parseMethod(method: KSFunctionDeclaration): Method {
         val name = method.qualifiedName?.getFullName() ?: method.simpleName.getFullName()
         if (!method.isAbstract) {
@@ -122,8 +157,8 @@
         if (method.typeParameters.isNotEmpty()) {
             logger.error(
                 "Error in $name: method cannot declare type parameters (<${
-                method.typeParameters.joinToString(limit = 3) { it.name.getShortName() }
-            }>)."
+                    method.typeParameters.joinToString(limit = 3) { it.name.getShortName() }
+                }>)."
             )
         }
         val invalidModifiers = method.modifiers.filterNot(validMethodModifiers::contains)
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/InterfaceParserTest.kt b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/InterfaceParserTest.kt
index 975ee9a..50109ac 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/InterfaceParserTest.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/InterfaceParserTest.kt
@@ -47,6 +47,10 @@
                         suspend fun doStuff(x: Int, y: Int?): String
                         suspend fun processList(list: List<Int>): List<String>
                         fun doMoreStuff()
+
+                        companion object {
+                            const val MY_CONST = 123
+                        }
                     }
                 """,
             )
@@ -238,20 +242,25 @@
     }
 
     @Test
-    fun interfaceWithCompanionObject_fails() {
+    fun interfaceWithCompanionObjectNonConstDeclarations_fails() {
         checkSourceFails(
                 serviceInterface(
                     """interface MySdk {
-                    |   companion object {
-                    |       fun foo() {}
-                    |   }
-                    |}
-                """
+                        |   companion object {
+                        |       const val CONST_IS_ALLOWED = 42
+                        |       val nonConstVal = 9
+                        |       fun method() = true
+                        |       class InnerClass() {}
+                        |   }
+                        |}
+                    """
                         .trimMargin()
                 )
             )
             .containsExactlyErrors(
-                "Error in com.mysdk.MySdk: annotated interfaces cannot declare companion objects."
+                "Error in com.mysdk.MySdk: companion object cannot declare non-const values (nonConstVal).",
+                "Error in com.mysdk.MySdk: companion object cannot declare methods (method).",
+                "Error in com.mysdk.MySdk: companion object cannot declare classes (InnerClass)."
             )
     }
 
diff --git a/privacysandbox/ui/integration-tests/mediateesdkprovider/lint-baseline.xml b/privacysandbox/ui/integration-tests/mediateesdkprovider/lint-baseline.xml
new file mode 100644
index 0000000..f7224db
--- /dev/null
+++ b/privacysandbox/ui/integration-tests/mediateesdkprovider/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
+
+    <issue
+        id="NewApi"
+        message="`&lt;androidx.privacysandbox.ui.integration.mediateesdkprovider.SdkProviderImpl>` requires API level 5 (current min is 0)"
+        errorLine1="        &lt;property android:name=&quot;android.sdksandbox.PROPERTY_SDK_PROVIDER_CLASS_NAME&quot;"
+        errorLine2="        ^">
+        <location
+            file="src/main/AndroidManifest.xml"/>
+    </issue>
+
+</issues>
diff --git a/privacysandbox/ui/integration-tests/testsdkprovider/lint-baseline.xml b/privacysandbox/ui/integration-tests/testsdkprovider/lint-baseline.xml
new file mode 100644
index 0000000..82f3286
--- /dev/null
+++ b/privacysandbox/ui/integration-tests/testsdkprovider/lint-baseline.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
+
+    <issue
+        id="NewApi"
+        message="`&lt;androidx.privacysandbox.ui.integration.testsdkprovider.SdkProviderImpl>` requires API level 5 (current min is 0)"
+        errorLine1="        &lt;property android:name=&quot;android.sdksandbox.PROPERTY_SDK_PROVIDER_CLASS_NAME&quot;"
+        errorLine2="        ^">
+        <location
+            file="src/main/AndroidManifest.xml"/>
+    </issue>
+
+</issues>
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 1621feb..d9dafed 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
@@ -101,7 +101,7 @@
             clientExecutor: Executor,
             client: SandboxedUiAdapter.SessionClient
         ) {
-            client.onSessionError(Exception("Error in openSession()"))
+            clientExecutor.execute { client.onSessionError(Exception("Error in openSession()")) }
         }
     }
 
@@ -133,13 +133,15 @@
         ) {
             internalClient = client
             testSession = TestSession(context, initialWidth, initialHeight, signalOptions)
-            if (!delayOpenSessionCallback) {
-                client.onSessionOpened(testSession!!)
+            clientExecutor.execute {
+                if (!delayOpenSessionCallback) {
+                    client.onSessionOpened(testSession!!)
+                }
+                isSessionOpened = true
+                this.isZOrderOnTop = isZOrderOnTop
+                this.inputToken = windowInputToken
+                openSessionLatch.countDown()
             }
-            isSessionOpened = true
-            this.isZOrderOnTop = isZOrderOnTop
-            this.inputToken = windowInputToken
-            openSessionLatch.countDown()
         }
 
         internal fun sendOnSessionOpened() {
diff --git a/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/util/TestSessionManager.kt b/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/util/TestSessionManager.kt
index 85868f9..6b52951 100644
--- a/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/util/TestSessionManager.kt
+++ b/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/util/TestSessionManager.kt
@@ -160,12 +160,14 @@
             this.initialWidth = initialWidth
             session =
                 if (hasFailingTestSession) {
-                    FailingTestSession(context, client)
+                    FailingTestSession(context, client, clientExecutor)
                 } else {
                     TestSession(context, client, placeViewInsideFrameLayout)
                 }
-            client.onSessionOpened(session)
-            openSessionLatch.countDown()
+            clientExecutor.execute {
+                client.onSessionOpened(session)
+                openSessionLatch.countDown()
+            }
         }
 
         /**
@@ -173,11 +175,14 @@
          */
         inner class FailingTestSession(
             private val context: Context,
-            sessionClient: SandboxedUiAdapter.SessionClient
+            sessionClient: SandboxedUiAdapter.SessionClient,
+            private val clientExecutor: Executor,
         ) : TestSession(context, sessionClient) {
             override val view: View
                 get() {
-                    sessionClient.onSessionError(Throwable("Test Session Exception"))
+                    clientExecutor.execute {
+                        sessionClient.onSessionError(Throwable("Test Session Exception"))
+                    }
                     return View(context)
                 }
 
diff --git a/recyclerview/recyclerview-lint/src/main/java/androidx/recyclerview/lint/RecyclerViewIssueRegistry.kt b/recyclerview/recyclerview-lint/src/main/java/androidx/recyclerview/lint/RecyclerViewIssueRegistry.kt
index 96f15ed..2c18e12 100644
--- a/recyclerview/recyclerview-lint/src/main/java/androidx/recyclerview/lint/RecyclerViewIssueRegistry.kt
+++ b/recyclerview/recyclerview-lint/src/main/java/androidx/recyclerview/lint/RecyclerViewIssueRegistry.kt
@@ -25,7 +25,7 @@
 
 class RecyclerViewIssueRegistry : IssueRegistry() {
     override val minApi = CURRENT_API
-    override val api = 14
+    override val api = 16
     override val issues: List<Issue>
         get() = listOf(InvalidSetHasFixedSizeDetector.ISSUE)
 
diff --git a/room/room-runtime-lint/src/main/java/androidx/room/lint/RoomIssueRegistry.kt b/room/room-runtime-lint/src/main/java/androidx/room/lint/RoomIssueRegistry.kt
index f2c540d..0a96419 100644
--- a/room/room-runtime-lint/src/main/java/androidx/room/lint/RoomIssueRegistry.kt
+++ b/room/room-runtime-lint/src/main/java/androidx/room/lint/RoomIssueRegistry.kt
@@ -23,7 +23,7 @@
 
 class RoomIssueRegistry : IssueRegistry() {
     override val minApi = CURRENT_API
-    override val api = 14
+    override val api = 16
     override val issues: List<Issue> =
         listOf(
             CursorKotlinUseIssueDetector.ISSUE,
diff --git a/room/room-runtime-lint/src/test/java/androidx/room/lint/ApiLintVersionsTest.kt b/room/room-runtime-lint/src/test/java/androidx/room/lint/ApiLintVersionsTest.kt
index 64f344c..09132f6 100644
--- a/room/room-runtime-lint/src/test/java/androidx/room/lint/ApiLintVersionsTest.kt
+++ b/room/room-runtime-lint/src/test/java/androidx/room/lint/ApiLintVersionsTest.kt
@@ -32,6 +32,6 @@
 
         val registry = RoomIssueRegistry()
         assertThat(registry.api).isEqualTo(CURRENT_API)
-        assertThat(registry.minApi).isEqualTo(14)
+        assertThat(registry.minApi).isEqualTo(16)
     }
 }
diff --git a/room/room-runtime/lint-baseline.xml b/room/room-runtime/lint-baseline.xml
index 244d55d..96db28a 100644
--- a/room/room-runtime/lint-baseline.xml
+++ b/room/room-runtime/lint-baseline.xml
@@ -1,41 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.7.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.7.0-alpha02)" variant="all" version="8.7.0-alpha02">
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="            val transaction = transactionStack.removeLastKt()"
-        errorLine2="                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/room/coroutines/ConnectionPoolImpl.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="                        invalidateTablesQueue.removeFirstKt()"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidUnitTest/kotlin/androidx/room/InvalidationTrackerTest.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="                    quoteStack.removeLastKt()"
-        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/room/util/SchemaInfoUtil.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="                    quoteStack.removeLastKt()"
-        errorLine2="                    ~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/commonMain/kotlin/androidx/room/util/SchemaInfoUtil.kt"/>
-    </issue>
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
 
     <issue
         id="RestrictedApiAndroidX"
diff --git a/settings.gradle b/settings.gradle
index 8594d6e..371584a 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -33,7 +33,7 @@
         if (agpOverride != null) {
             classpath("com.android.settings:com.android.settings.gradle.plugin:$agpOverride")
         } else {
-            classpath("com.android.settings:com.android.settings.gradle.plugin:8.7.0-alpha02")
+            classpath("com.android.settings:com.android.settings.gradle.plugin:8.8.0-alpha01")
         }
     }
 }
@@ -797,7 +797,7 @@
 includeProject(":lifecycle:lifecycle-viewmodel-compose:integration-tests:lifecycle-viewmodel-demos", [BuildType.COMPOSE])
 includeProject(":lifecycle:lifecycle-viewmodel-compose-lint", [BuildType.COMPOSE])
 includeProject(":lifecycle:lifecycle-viewmodel-ktx", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
-includeProject(":lifecycle:lifecycle-viewmodel-savedstate", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE])
+includeProject(":lifecycle:lifecycle-viewmodel-savedstate", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE, BuildType.KMP])
 includeProject(":lifecycle:lifecycle-viewmodel-testing", [BuildType.MAIN, BuildType.FLAN, BuildType.COMPOSE, BuildType.INFRAROGUE, BuildType.KMP])
 includeProject(":lint:lint-gradle", [BuildType.MAIN])
 includeProject(":lint-checks")
diff --git a/sharetarget/integration-tests/testapp/lint-baseline.xml b/sharetarget/integration-tests/testapp/lint-baseline.xml
index e3c714d..04bbf91 100644
--- a/sharetarget/integration-tests/testapp/lint-baseline.xml
+++ b/sharetarget/integration-tests/testapp/lint-baseline.xml
@@ -1,5 +1,14 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 7.4.0-alpha08" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0-alpha08)" variant="all" version="7.4.0-alpha08">
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
+
+    <issue
+        id="NewApi"
+        message="`&lt;androidx.sharetarget.ChooserTargetServiceCompat>` requires API level 23 (current min is 21)"
+        errorLine1="            &lt;meta-data"
+        errorLine2="            ^">
+        <location
+            file="src/main/AndroidManifest.xml"/>
+    </issue>
 
     <issue
         id="UnknownNullness"
diff --git a/sharetarget/sharetarget/lint-baseline.xml b/sharetarget/sharetarget/lint-baseline.xml
index 89ef90a..d0a4613 100644
--- a/sharetarget/sharetarget/lint-baseline.xml
+++ b/sharetarget/sharetarget/lint-baseline.xml
@@ -1,5 +1,14 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-alpha10" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha10)" variant="all" version="8.3.0-alpha10">
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
+
+    <issue
+        id="NewApi"
+        message="`&lt;androidx.sharetarget.ChooserTargetServiceCompat>` requires API level 23 (current min is 21)"
+        errorLine1="        &lt;service android:name=&quot;.ChooserTargetServiceCompat&quot;"
+        errorLine2="        ^">
+        <location
+            file="src/main/AndroidManifest.xml"/>
+    </issue>
 
     <issue
         id="UnknownNullness"
diff --git a/startup/startup-runtime-lint/src/main/java/androidx/startup/lint/StartupRuntimeIssueRegistry.kt b/startup/startup-runtime-lint/src/main/java/androidx/startup/lint/StartupRuntimeIssueRegistry.kt
index 2bba984..b24665b 100644
--- a/startup/startup-runtime-lint/src/main/java/androidx/startup/lint/StartupRuntimeIssueRegistry.kt
+++ b/startup/startup-runtime-lint/src/main/java/androidx/startup/lint/StartupRuntimeIssueRegistry.kt
@@ -26,7 +26,7 @@
 /** The [IssueRegistry] for `androidx.startup`. */
 class StartupRuntimeIssueRegistry : IssueRegistry() {
     override val minApi = CURRENT_API
-    override val api = 14
+    override val api = 16
     override val issues: List<Issue>
         get() =
             listOf(InitializerConstructorDetector.ISSUE, EnsureInitializerMetadataDetector.ISSUE)
diff --git a/test/uiautomator/integration-tests/testapp/lint-baseline.xml b/test/uiautomator/integration-tests/testapp/lint-baseline.xml
index ec1555e..8f4715c 100644
--- a/test/uiautomator/integration-tests/testapp/lint-baseline.xml
+++ b/test/uiautomator/integration-tests/testapp/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.4.0-alpha09" type="baseline" client="gradle" dependencies="false" name="AGP (8.4.0-alpha09)" variant="all" version="8.4.0-alpha09">
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
 
     <issue
         id="MissingPermission"
@@ -11,6 +11,24 @@
     </issue>
 
     <issue
+        id="NewApi"
+        message="`&lt;androidx.test.uiautomator.testapp.DragTestActivity>` requires API level 24 (current min is 21)"
+        errorLine1="        &lt;activity android:name=&quot;.DragTestActivity&quot;"
+        errorLine2="        ^">
+        <location
+            file="src/main/AndroidManifest.xml"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="`&lt;androidx.test.uiautomator.testapp.SplitScreenTestActivity>` requires API level 32 (current min is 21)"
+        errorLine1="        &lt;activity android:name=&quot;.SplitScreenTestActivity&quot;"
+        errorLine2="        ^">
+        <location
+            file="src/main/AndroidManifest.xml"/>
+    </issue>
+
+    <issue
         id="PrimitiveInCollection"
         message="field mOrder with type List&lt;Integer>: replace with IntList"
         errorLine1="        private final List&lt;Integer> mOrder = Arrays.asList(0, 1, 2);"
diff --git a/wear/compose/compose-material3/api/current.txt b/wear/compose/compose-material3/api/current.txt
index 9a7ca87..bbe3828 100644
--- a/wear/compose/compose-material3/api/current.txt
+++ b/wear/compose/compose-material3/api/current.txt
@@ -6,20 +6,19 @@
     method @androidx.compose.runtime.Composable public void ConfirmButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public void DismissButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public void GroupSeparator();
+    method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.PaddingValues confirmDismissContentPadding();
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.PaddingValues contentPadding(boolean hasBottomButton);
     method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> getConfirmIcon();
     method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> getDismissIcon();
-    method public float getEdgeButtonExtraTopPadding();
     method public androidx.compose.foundation.layout.Arrangement.Vertical getVerticalArrangement();
     property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> ConfirmIcon;
     property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> DismissIcon;
     property public final androidx.compose.foundation.layout.Arrangement.Vertical VerticalArrangement;
-    property public final float edgeButtonExtraTopPadding;
     field public static final androidx.wear.compose.material3.AlertDialogDefaults INSTANCE;
   }
 
   public final class AlertDialogKt {
-    method @androidx.compose.runtime.Composable public static void AlertDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> bottomButton, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.window.DialogProperties properties, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit>? content);
+    method @androidx.compose.runtime.Composable public static void AlertDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? bottomButton, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.window.DialogProperties properties, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit>? content);
     method @androidx.compose.runtime.Composable public static void AlertDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> confirmButton, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> dismissButton, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.window.DialogProperties properties, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit>? content);
   }
 
@@ -453,7 +452,7 @@
   }
 
   public final class EdgeButtonKt {
-    method @androidx.compose.runtime.Composable public static void EdgeButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional float buttonHeight, optional boolean enabled, optional androidx.wear.compose.material3.ButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void EdgeButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional float preferredHeight, optional boolean enabled, optional androidx.wear.compose.material3.ButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
   }
 
   @SuppressCompatibility @kotlin.RequiresOptIn(message="This Wear Material3 API is experimental and is likely to change or to be removed in" + " the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalWearMaterial3Api {
@@ -694,8 +693,8 @@
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.OpenOnPhoneDialogColors colors();
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.OpenOnPhoneDialogColors colors(optional long iconColor, optional long iconContainerColor, optional long progressIndicatorColor, optional long progressTrackColor, optional long textColor);
     method @androidx.compose.runtime.Composable public kotlin.jvm.functions.Function1<androidx.wear.compose.foundation.CurvedScope,kotlin.Unit> curvedText(optional String text, optional androidx.wear.compose.foundation.CurvedTextStyle style);
-    method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit> getIcon();
-    property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit> Icon;
+    method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit> getOpenOnPhoneIcon();
+    property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit> OpenOnPhoneIcon;
     field public static final long DurationMillis = 4000L; // 0xfa0L
     field public static final androidx.wear.compose.material3.OpenOnPhoneDialogDefaults INSTANCE;
   }
diff --git a/wear/compose/compose-material3/api/restricted_current.txt b/wear/compose/compose-material3/api/restricted_current.txt
index 9a7ca87..bbe3828 100644
--- a/wear/compose/compose-material3/api/restricted_current.txt
+++ b/wear/compose/compose-material3/api/restricted_current.txt
@@ -6,20 +6,19 @@
     method @androidx.compose.runtime.Composable public void ConfirmButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public void DismissButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public void GroupSeparator();
+    method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.PaddingValues confirmDismissContentPadding();
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.PaddingValues contentPadding(boolean hasBottomButton);
     method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> getConfirmIcon();
     method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> getDismissIcon();
-    method public float getEdgeButtonExtraTopPadding();
     method public androidx.compose.foundation.layout.Arrangement.Vertical getVerticalArrangement();
     property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> ConfirmIcon;
     property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.RowScope,kotlin.Unit> DismissIcon;
     property public final androidx.compose.foundation.layout.Arrangement.Vertical VerticalArrangement;
-    property public final float edgeButtonExtraTopPadding;
     field public static final androidx.wear.compose.material3.AlertDialogDefaults INSTANCE;
   }
 
   public final class AlertDialogKt {
-    method @androidx.compose.runtime.Composable public static void AlertDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> bottomButton, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.window.DialogProperties properties, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit>? content);
+    method @androidx.compose.runtime.Composable public static void AlertDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? bottomButton, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.window.DialogProperties properties, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit>? content);
     method @androidx.compose.runtime.Composable public static void AlertDialog(boolean show, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> confirmButton, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> dismissButton, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? text, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.window.DialogProperties properties, optional kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit>? content);
   }
 
@@ -453,7 +452,7 @@
   }
 
   public final class EdgeButtonKt {
-    method @androidx.compose.runtime.Composable public static void EdgeButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional float buttonHeight, optional boolean enabled, optional androidx.wear.compose.material3.ButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void EdgeButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional float preferredHeight, optional boolean enabled, optional androidx.wear.compose.material3.ButtonColors colors, optional androidx.compose.foundation.BorderStroke? border, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
   }
 
   @SuppressCompatibility @kotlin.RequiresOptIn(message="This Wear Material3 API is experimental and is likely to change or to be removed in" + " the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalWearMaterial3Api {
@@ -694,8 +693,8 @@
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.OpenOnPhoneDialogColors colors();
     method @androidx.compose.runtime.Composable public androidx.wear.compose.material3.OpenOnPhoneDialogColors colors(optional long iconColor, optional long iconContainerColor, optional long progressIndicatorColor, optional long progressTrackColor, optional long textColor);
     method @androidx.compose.runtime.Composable public kotlin.jvm.functions.Function1<androidx.wear.compose.foundation.CurvedScope,kotlin.Unit> curvedText(optional String text, optional androidx.wear.compose.foundation.CurvedTextStyle style);
-    method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit> getIcon();
-    property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit> Icon;
+    method public kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit> getOpenOnPhoneIcon();
+    property public final kotlin.jvm.functions.Function1<androidx.compose.foundation.layout.BoxScope,kotlin.Unit> OpenOnPhoneIcon;
     field public static final long DurationMillis = 4000L; // 0xfa0L
     field public static final androidx.wear.compose.material3.OpenOnPhoneDialogDefaults INSTANCE;
   }
diff --git a/wear/compose/compose-material3/build.gradle b/wear/compose/compose-material3/build.gradle
index fb86c7e..b52ba64 100644
--- a/wear/compose/compose-material3/build.gradle
+++ b/wear/compose/compose-material3/build.gradle
@@ -21,6 +21,8 @@
  * Please use that script when creating a new project, rather than copying an existing project and
  * modifying its settings.
  */
+
+import androidx.build.KotlinTarget
 import androidx.build.LibraryType
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
@@ -94,6 +96,7 @@
     legacyDisableKotlinStrictApiMode = true
     metalavaK2UastEnabled = false
     samples(project(":wear:compose:compose-material3-samples"))
+    kotlinTarget = KotlinTarget.KOTLIN_1_9
 }
 
 tasks.withType(KotlinCompile).configureEach {
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/AlertDialogs.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/AlertDialogs.kt
index 4440425..c8618c6 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/AlertDialogs.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/AlertDialogs.kt
@@ -23,6 +23,9 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.material.icons.rounded.AccountCircle
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -41,6 +44,8 @@
 import androidx.wear.compose.material3.AlertDialog
 import androidx.wear.compose.material3.AlertDialogDefaults
 import androidx.wear.compose.material3.Button
+import androidx.wear.compose.material3.FilledTonalButton
+import androidx.wear.compose.material3.Icon
 import androidx.wear.compose.material3.ListHeader
 import androidx.wear.compose.material3.MaterialTheme
 import androidx.wear.compose.material3.RadioButton
@@ -56,6 +61,7 @@
         ComposableDemo("Bottom button") { AlertDialogWithBottomButtonSample() },
         ComposableDemo("Confirm and Dismiss") { AlertDialogWithConfirmAndDismissSample() },
         ComposableDemo("Content groups") { AlertDialogWithContentGroupsSample() },
+        ComposableDemo("Button stack") { AlertDialogWithButtonStack() },
         ComposableDemo("AlertDialog builder") { AlertDialogBuilder() },
     )
 
@@ -67,7 +73,7 @@
     var showMessage by remember { mutableStateOf(false) }
     var showSecondaryButton by remember { mutableStateOf(false) }
     var showCaption by remember { mutableStateOf(false) }
-    var showTwoButtons by remember { mutableStateOf(false) }
+    var buttonsType by remember { mutableStateOf(AlertButtonsType.BOTTOM_BUTTON) }
 
     var showDialog by remember { mutableStateOf(false) }
 
@@ -121,19 +127,27 @@
             item {
                 RadioButton(
                     modifier = Modifier.fillMaxWidth(),
-                    selected = !showTwoButtons,
-                    onSelect = { showTwoButtons = false },
+                    selected = buttonsType == AlertButtonsType.BOTTOM_BUTTON,
+                    onSelect = { buttonsType = AlertButtonsType.BOTTOM_BUTTON },
                     label = { Text("Single Bottom button") },
                 )
             }
             item {
                 RadioButton(
                     modifier = Modifier.fillMaxWidth(),
-                    selected = showTwoButtons,
-                    onSelect = { showTwoButtons = true },
+                    selected = buttonsType == AlertButtonsType.CONFIRM_DISMISS,
+                    onSelect = { buttonsType = AlertButtonsType.CONFIRM_DISMISS },
                     label = { Text("Ok/Cancel buttons") },
                 )
             }
+            item {
+                RadioButton(
+                    modifier = Modifier.fillMaxWidth(),
+                    selected = buttonsType == AlertButtonsType.NO_BUTTONS,
+                    onSelect = { buttonsType = AlertButtonsType.NO_BUTTONS },
+                    label = { Text("No bottom button") },
+                )
+            }
             item { Button(onClick = { showDialog = true }, label = { Text("Show dialog") }) }
         }
     }
@@ -145,7 +159,7 @@
             showCaption = showCaption,
             showSecondaryButton = showSecondaryButton,
             showMessage = showMessage,
-            showTwoButtons = showTwoButtons,
+            buttonsType = buttonsType,
             onConfirmButton = { showDialog = false },
             onDismissRequest = { showDialog = false }
         )
@@ -153,6 +167,60 @@
 }
 
 @Composable
+fun AlertDialogWithButtonStack() {
+    var showDialog by remember { mutableStateOf(false) }
+
+    Box(Modifier.fillMaxSize()) {
+        FilledTonalButton(
+            modifier = Modifier.align(Alignment.Center),
+            onClick = { showDialog = true },
+            label = { Text("Show Dialog") }
+        )
+    }
+
+    AlertDialog(
+        show = showDialog,
+        onDismissRequest = { showDialog = false },
+        icon = {
+            Icon(
+                Icons.Rounded.AccountCircle,
+                modifier = Modifier.size(32.dp),
+                contentDescription = null,
+                tint = MaterialTheme.colorScheme.primary
+            )
+        },
+        title = { Text("Allow access to your photos?") },
+        text = { Text("Lerp ipsum dolor sit amet.") },
+        bottomButton = null,
+    ) {
+        item {
+            Button(
+                modifier = Modifier.fillMaxWidth(),
+                onClick = {},
+                label = { Text("While using app") },
+                icon = { Icon(Icons.Filled.Check, "Check") }
+            )
+        }
+        item {
+            FilledTonalButton(
+                modifier = Modifier.fillMaxWidth(),
+                onClick = {},
+                label = { Text("Ask every time") },
+                icon = { Icon(Icons.Filled.Check, "Check") }
+            )
+        }
+        item {
+            FilledTonalButton(
+                modifier = Modifier.fillMaxWidth(),
+                onClick = {},
+                label = { Text("Don't allow") },
+                icon = { Icon(Icons.Filled.Check, "Check") }
+            )
+        }
+    }
+}
+
+@Composable
 private fun CustomAlertDialog(
     show: Boolean,
     onDismissRequest: () -> Unit,
@@ -160,7 +228,7 @@
     properties: DialogProperties = DialogProperties(),
     showIcon: Boolean,
     onConfirmButton: () -> Unit,
-    showTwoButtons: Boolean,
+    buttonsType: AlertButtonsType,
     showMessage: Boolean,
     showSecondaryButton: Boolean,
     showCaption: Boolean,
@@ -183,15 +251,15 @@
                 { Message() }
             } else null,
         onConfirmButton =
-            if (showTwoButtons) {
+            if (buttonsType == AlertButtonsType.CONFIRM_DISMISS) {
                 onConfirmButton
             } else null,
         onDismissButton =
-            if (showTwoButtons) {
+            if (buttonsType == AlertButtonsType.CONFIRM_DISMISS) {
                 { /* dismiss action */ }
             } else null,
         onBottomButton =
-            if (!showTwoButtons) {
+            if (buttonsType == AlertButtonsType.BOTTOM_BUTTON) {
                 onConfirmButton
             } else null,
         content =
@@ -202,7 +270,7 @@
                     }
                     if (showCaption) {
                         item { Caption(captionHorizontalPadding) }
-                        if (!showTwoButtons) {
+                        if (buttonsType == AlertButtonsType.BOTTOM_BUTTON) {
                             item { AlertDialogDefaults.GroupSeparator() }
                         }
                     }
@@ -279,7 +347,7 @@
             dismissButton = { AlertDialogDefaults.DismissButton(onDismissButton) },
             content = content
         )
-    } else if (onBottomButton != null) {
+    } else
         AlertDialog(
             show = show,
             onDismissRequest = onDismissRequest,
@@ -288,8 +356,16 @@
             title = title,
             icon = icon,
             text = message,
-            bottomButton = { AlertDialogDefaults.BottomButton(onBottomButton) },
+            bottomButton =
+                if (onBottomButton != null) {
+                    { AlertDialogDefaults.BottomButton(onBottomButton) }
+                } else null,
             content = content
         )
-    }
+}
+
+private enum class AlertButtonsType {
+    NO_BUTTONS,
+    BOTTOM_BUTTON,
+    CONFIRM_DISMISS
 }
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/EdgeButtonDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/EdgeButtonDemo.kt
index d93f73d..ba4a754 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/EdgeButtonDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/EdgeButtonDemo.kt
@@ -72,7 +72,7 @@
             bottomButton = {
                 EdgeButton(
                     onClick = {},
-                    buttonHeight = ButtonDefaults.EdgeButtonHeightLarge,
+                    preferredHeight = ButtonDefaults.EdgeButtonHeightLarge,
                     colors = ButtonDefaults.buttonColors(containerColor = Color.DarkGray)
                 ) {
                     Text(labels[selectedLabel.intValue], color = Color.White)
@@ -120,7 +120,7 @@
             bottomButton = {
                 EdgeButton(
                     onClick = {},
-                    buttonHeight = ButtonDefaults.EdgeButtonHeightLarge,
+                    preferredHeight = ButtonDefaults.EdgeButtonHeightLarge,
                     colors = ButtonDefaults.buttonColors(containerColor = Color.DarkGray)
                 ) {
                     Text(labels[selectedLabel.intValue], color = Color.White)
@@ -215,7 +215,7 @@
             EdgeButton(
                 onClick = {},
                 enabled = colorNames[color] != "D",
-                buttonHeight = sizes[size],
+                preferredHeight = sizes[size],
                 colors = colors[color],
                 border =
                     if (colorNames[color] == "O")
@@ -257,7 +257,7 @@
             bottomButton = {
                 EdgeButton(
                     onClick = {},
-                    buttonHeight = sizes[selectedSize].second,
+                    preferredHeight = sizes[selectedSize].second,
                     colors = colors[selectedColor].second,
                     border =
                         if (colors[selectedColor].first == "Outlined")
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Haptics.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/HapticsDemo.kt
similarity index 65%
rename from wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Haptics.kt
rename to wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/HapticsDemo.kt
index 8a9bbea..4ecf02f 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/Haptics.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/HapticsDemo.kt
@@ -16,14 +16,29 @@
 
 package androidx.wear.compose.material3.demos
 
+import android.os.Build
+import android.os.VibrationEffect
+import android.os.Vibrator
 import android.view.HapticFeedbackConstants
 import android.view.View
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.material.icons.filled.Close
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
 import androidx.wear.compose.material3.Button
+import androidx.wear.compose.material3.Icon
 import androidx.wear.compose.material3.ListHeader
 import androidx.wear.compose.material3.Text
 
@@ -63,7 +78,26 @@
             Pair(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE, "Virtual Key Release"),
         )
 
+    val premiumVibratorEnabled = isPremiumVibratorEnabled()
+
     ScalingLazyDemo {
+        item { ListHeader { Text("Premium Haptics") } }
+        item {
+            Row(
+                modifier = Modifier.fillMaxWidth(),
+                horizontalArrangement = Arrangement.Center,
+                verticalAlignment = Alignment.CenterVertically
+            ) {
+                Icon(
+                    imageVector =
+                        if (premiumVibratorEnabled) Icons.Filled.Check else Icons.Filled.Close,
+                    contentDescription = "Premium Haptics Status",
+                    tint = if (premiumVibratorEnabled) Color.Green else Color.Red
+                )
+                Spacer(modifier = Modifier.width(8.dp))
+                Text(if (premiumVibratorEnabled) "Enabled" else "Disabled")
+            }
+        }
         item { ListHeader { Text("Haptic Constants") } }
         items(hapticConstants.size) { index ->
             val (constant, name) = hapticConstants[index]
@@ -92,3 +126,23 @@
         view.performHapticFeedback(feedbackConstant)
     }
 }
+
+@Composable
+fun isPremiumVibratorEnabled(): Boolean {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+        // NB whilst the 'areAllPrimitivesSupported' API needs R (API 30), we need S (API
+        // 31) so that PRIMITIVE_THUD is available.
+        val vibrator = LocalContext.current.getSystemService(Vibrator::class.java)
+        if (
+            vibrator.areAllPrimitivesSupported(
+                VibrationEffect.Composition.PRIMITIVE_CLICK,
+                VibrationEffect.Composition.PRIMITIVE_TICK,
+                VibrationEffect.Composition.PRIMITIVE_THUD
+            )
+        ) {
+            return true
+        }
+    }
+
+    return false
+}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimePickerDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimePickerDemo.kt
index 92d8d87..20d4ca7 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimePickerDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/TimePickerDemo.kt
@@ -18,11 +18,8 @@
 
 import android.os.Build
 import androidx.annotation.RequiresApi
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.height
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Edit
 import androidx.compose.runtime.Composable
@@ -32,7 +29,6 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
 import androidx.wear.compose.integration.demos.common.ComposableDemo
 import androidx.wear.compose.material3.Button
 import androidx.wear.compose.material3.Icon
@@ -71,16 +67,14 @@
             initialTime = timePickerTime
         )
     } else {
-        Column(
+        Box(
             modifier = Modifier.fillMaxSize(),
-            verticalArrangement = Arrangement.Center,
-            horizontalAlignment = Alignment.CenterHorizontally
+            contentAlignment = Alignment.Center,
         ) {
-            Text("Selected Time")
-            Spacer(Modifier.height(12.dp))
             Button(
                 onClick = { showTimePicker = true },
-                label = { Text(timePickerTime.format(formatter)) },
+                label = { Text("Selected Time") },
+                secondaryLabel = { Text(timePickerTime.format(formatter)) },
                 icon = { Icon(imageVector = Icons.Filled.Edit, contentDescription = "Edit") },
             )
         }
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
index fb753ac..6c215c1 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
@@ -46,6 +46,7 @@
     Material3DemoCategory(
         "Material 3",
         listOf(
+            ComposableDemo("Haptics") { Centralize { HapticsDemos() } },
             Material3DemoCategory(title = "Typography", TypographyDemos),
             Material3DemoCategory(
                 "Button",
@@ -69,7 +70,6 @@
             Material3DemoCategory("Open on phone Dialog", OpenOnPhoneDialogDemos),
             ComposableDemo("Scaffold") { ScaffoldSample() },
             Material3DemoCategory("ScrollAway", ScrollAwayDemos),
-            ComposableDemo("Haptics") { Centralize { HapticsDemos() } },
             ComposableDemo("Compact Button") { CompactButtonDemo() },
             ComposableDemo("Icon Button") { IconButtonDemo() },
             ComposableDemo("Image Button") { ImageButtonDemo() },
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/EdgeButtonSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/EdgeButtonSample.kt
index 122d612..2aa006b 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/EdgeButtonSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/EdgeButtonSample.kt
@@ -47,7 +47,7 @@
         Text("Confirm", Modifier.align(Alignment.Center))
         EdgeButton(
             onClick = { /* Do something */ },
-            buttonHeight = ButtonDefaults.EdgeButtonHeightMedium,
+            preferredHeight = ButtonDefaults.EdgeButtonHeightMedium,
             modifier = Modifier.align(Alignment.BottomEnd)
         ) {
             Icon(
@@ -68,7 +68,7 @@
         bottomButton = {
             EdgeButton(
                 onClick = {},
-                buttonHeight = ButtonDefaults.EdgeButtonHeightLarge,
+                preferredHeight = ButtonDefaults.EdgeButtonHeightLarge,
                 colors = buttonColors(containerColor = Color.DarkGray)
             ) {
                 Text("Ok", textAlign = TextAlign.Center)
diff --git a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TimePickerSample.kt b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TimePickerSample.kt
index 78cc200..d136db7 100644
--- a/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TimePickerSample.kt
+++ b/wear/compose/compose-material3/samples/src/main/java/androidx/wear/compose/material3/samples/TimePickerSample.kt
@@ -17,11 +17,8 @@
 package androidx.wear.compose.material3.samples
 
 import androidx.annotation.Sampled
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.height
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Edit
 import androidx.compose.runtime.Composable
@@ -32,7 +29,6 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.unit.dp
 import androidx.wear.compose.material3.Button
 import androidx.wear.compose.material3.Icon
 import androidx.wear.compose.material3.Text
@@ -59,16 +55,14 @@
             initialTime = timePickerTime // Initialize with last picked time on reopen
         )
     } else {
-        Column(
+        Box(
             modifier = Modifier.fillMaxSize(),
-            verticalArrangement = Arrangement.Center,
-            horizontalAlignment = Alignment.CenterHorizontally
+            contentAlignment = Alignment.Center,
         ) {
-            Text("Selected Time")
-            Spacer(Modifier.height(12.dp))
             Button(
                 onClick = { showTimePicker = true },
-                label = { Text(timePickerTime.format(formatter)) },
+                label = { Text("Selected Time") },
+                secondaryLabel = { Text(timePickerTime.format(formatter)) },
                 icon = { Icon(imageVector = Icons.Filled.Edit, contentDescription = "Edit") },
             )
         }
@@ -91,16 +85,14 @@
             initialTime = timePickerTime // Initialize with last picked time on reopen
         )
     } else {
-        Column(
+        Box(
             modifier = Modifier.fillMaxSize(),
-            verticalArrangement = Arrangement.Center,
-            horizontalAlignment = Alignment.CenterHorizontally
+            contentAlignment = Alignment.Center,
         ) {
-            Text("Selected Time")
-            Spacer(Modifier.height(12.dp))
             Button(
                 onClick = { showTimePicker = true },
-                label = { Text(timePickerTime.format(formatter)) },
+                label = { Text("Selected Time") },
+                secondaryLabel = { Text(timePickerTime.format(formatter)) },
                 icon = { Icon(imageVector = Icons.Filled.Edit, contentDescription = "Edit") },
             )
         }
@@ -123,16 +115,14 @@
             initialTime = timePickerTime // Initialize with last picked time on reopen
         )
     } else {
-        Column(
+        Box(
             modifier = Modifier.fillMaxSize(),
-            verticalArrangement = Arrangement.Center,
-            horizontalAlignment = Alignment.CenterHorizontally
+            contentAlignment = Alignment.Center,
         ) {
-            Text("Selected Time")
-            Spacer(Modifier.height(12.dp))
             Button(
                 onClick = { showTimePicker = true },
-                label = { Text(timePickerTime.format(formatter)) },
+                label = { Text("Selected Time") },
+                secondaryLabel = { Text(timePickerTime.format(formatter)) },
                 icon = { Icon(imageVector = Icons.Filled.Edit, contentDescription = "Edit") },
             )
         }
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AlertDialogTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AlertDialogTest.kt
index 076610a..a987c30 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AlertDialogTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/AlertDialogTest.kt
@@ -19,6 +19,8 @@
 import android.os.Build
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -37,6 +39,7 @@
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.test.swipeRight
+import androidx.compose.ui.test.swipeUp
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
@@ -325,7 +328,7 @@
             expectedContentColor = MaterialTheme.colorScheme.onBackground
             expectedTextStyle = MaterialTheme.typography.titleMedium
             expectedTextAlign = TextAlign.Center
-            expectedTextMaxLines = AlertDialogDefaults.titleMaxLines
+            expectedTextMaxLines = AlertTitleMaxLines
             AlertDialog(
                 modifier = Modifier.testTag(TEST_TAG),
                 title = {
@@ -439,46 +442,87 @@
     @Test
     fun with_title_confirmDismissButtons_positioning() {
         rule.setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
-            AlertDialog(
-                show = true,
-                title = { Text("Title", modifier = Modifier.testTag(TitleTestTag)) },
-                onDismissRequest = {},
-                confirmButton = {
-                    Button(onClick = {}, modifier = Modifier.testTag(ConfirmButtonTestTag)) {}
-                },
-                dismissButton = {
-                    Button(onClick = {}, modifier = Modifier.testTag(DismissButtonTestTag)) {}
-                },
-                verticalArrangement =
-                    Arrangement.spacedBy(space = 0.dp, alignment = Alignment.CenterVertically),
-                modifier = Modifier.testTag(TEST_TAG),
-            )
+            ScreenConfiguration(AlertScreenSize) {
+                AlertDialog(
+                    show = true,
+                    title = { Text("Title", modifier = Modifier.testTag(TitleTestTag)) },
+                    onDismissRequest = {},
+                    confirmButton = {
+                        Button(onClick = {}, modifier = Modifier.testTag(ConfirmButtonTestTag)) {}
+                    },
+                    dismissButton = {
+                        Button(onClick = {}, modifier = Modifier.testTag(DismissButtonTestTag)) {}
+                    },
+                    verticalArrangement =
+                        Arrangement.spacedBy(space = 0.dp, alignment = Alignment.CenterVertically),
+                    modifier = Modifier.size(AlertScreenSize.dp).testTag(TEST_TAG),
+                )
+            }
         }
 
         val titleBottom = rule.onNodeWithTag(TitleTestTag).getUnclippedBoundsInRoot().bottom
         val confirmButtonTop =
             rule.onNodeWithTag(ConfirmButtonTestTag).getUnclippedBoundsInRoot().top
-        confirmButtonTop.assertIsEqualTo(titleBottom + AlertDialogDefaults.bottomSpacing)
+        confirmButtonTop.assertIsEqualTo(titleBottom + AlertBottomSpacing)
+    }
+
+    @Test
+    fun with_title_noBottomButton_positioning() {
+        rule.setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
+            ScreenConfiguration(SmallScreenSize) {
+                AlertDialog(
+                    show = true,
+                    title = { Text("Title") },
+                    onDismissRequest = {},
+                    bottomButton = null,
+                    verticalArrangement =
+                        Arrangement.spacedBy(space = 0.dp, alignment = Alignment.CenterVertically),
+                    modifier = Modifier.size(SmallScreenSize.dp).testTag(TEST_TAG),
+                ) {
+                    item {
+                        Text(
+                            "ContentText",
+                            // We set height larger than the screen size to be sure that the list
+                            // will be scrollable
+                            modifier =
+                                Modifier.size(width = 100.dp, height = (SmallScreenSize + 50).dp)
+                                    .testTag(ContentTestTag)
+                        )
+                    }
+                }
+            }
+        }
+        rule.onNodeWithTag(TEST_TAG).performTouchInput { swipeUp() }
+
+        val contentBottom = rule.onNodeWithTag(ContentTestTag).getUnclippedBoundsInRoot().bottom
+        val alertDialogBottom = rule.onNodeWithTag(TEST_TAG).getUnclippedBoundsInRoot().bottom
+        // Assert that there is a proper padding between the bottom of the content and the bottom of
+        // the dialog.
+        contentBottom.assertIsEqualTo(
+            alertDialogBottom * (1 - AlertDialogDefaults.noEdgeButtonBottomPaddingFraction)
+        )
     }
 
     @Test
     fun with_icon_title_confirmDismissButtons_positioning() {
         rule.setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
-            AlertDialog(
-                show = true,
-                icon = { TestImage(IconTestTag) },
-                title = { Text("Title", modifier = Modifier.testTag(TitleTestTag)) },
-                onDismissRequest = {},
-                confirmButton = {
-                    Button(onClick = {}, modifier = Modifier.testTag(ConfirmButtonTestTag)) {}
-                },
-                dismissButton = {
-                    Button(onClick = {}, modifier = Modifier.testTag(DismissButtonTestTag)) {}
-                },
-                verticalArrangement =
-                    Arrangement.spacedBy(space = 0.dp, alignment = Alignment.CenterVertically),
-                modifier = Modifier.testTag(TEST_TAG),
-            )
+            ScreenConfiguration(AlertScreenSize) {
+                AlertDialog(
+                    show = true,
+                    icon = { TestImage(IconTestTag) },
+                    title = { Text("Title", modifier = Modifier.testTag(TitleTestTag)) },
+                    onDismissRequest = {},
+                    confirmButton = {
+                        Button(onClick = {}, modifier = Modifier.testTag(ConfirmButtonTestTag)) {}
+                    },
+                    dismissButton = {
+                        Button(onClick = {}, modifier = Modifier.testTag(DismissButtonTestTag)) {}
+                    },
+                    verticalArrangement =
+                        Arrangement.spacedBy(space = 0.dp, alignment = Alignment.CenterVertically),
+                    modifier = Modifier.size(AlertScreenSize.dp).testTag(TEST_TAG),
+                )
+            }
         }
 
         val iconBottom = rule.onNodeWithTag(IconTestTag).getUnclippedBoundsInRoot().bottom
@@ -487,29 +531,31 @@
         val confirmButtonTop =
             rule.onNodeWithTag(ConfirmButtonTestTag).getUnclippedBoundsInRoot().top
 
-        titleTop.assertIsEqualTo(iconBottom + AlertDialogDefaults.iconBottomSpacing)
-        confirmButtonTop.assertIsEqualTo(titleBottom + AlertDialogDefaults.bottomSpacing)
+        titleTop.assertIsEqualTo(iconBottom + AlertIconBottomSpacing)
+        confirmButtonTop.assertIsEqualTo(titleBottom + AlertBottomSpacing)
     }
 
     @Test
     fun with_icon_title_textMessage_confirmDismissButtons_positioning() {
         rule.setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
-            AlertDialog(
-                show = true,
-                icon = { TestImage(IconTestTag) },
-                title = { Text("Title", modifier = Modifier.testTag(TitleTestTag)) },
-                text = { Text("Text", modifier = Modifier.testTag(TextTestTag)) },
-                onDismissRequest = {},
-                confirmButton = {
-                    Button(onClick = {}, modifier = Modifier.testTag(ConfirmButtonTestTag)) {}
-                },
-                dismissButton = {
-                    Button(onClick = {}, modifier = Modifier.testTag(DismissButtonTestTag)) {}
-                },
-                verticalArrangement =
-                    Arrangement.spacedBy(space = 0.dp, alignment = Alignment.CenterVertically),
-                modifier = Modifier.testTag(TEST_TAG),
-            )
+            ScreenConfiguration(AlertScreenSize) {
+                AlertDialog(
+                    show = true,
+                    icon = { TestImage(IconTestTag) },
+                    title = { Text("Title", modifier = Modifier.testTag(TitleTestTag)) },
+                    text = { Text("Text", modifier = Modifier.testTag(TextTestTag)) },
+                    onDismissRequest = {},
+                    confirmButton = {
+                        Button(onClick = {}, modifier = Modifier.testTag(ConfirmButtonTestTag)) {}
+                    },
+                    dismissButton = {
+                        Button(onClick = {}, modifier = Modifier.testTag(DismissButtonTestTag)) {}
+                    },
+                    verticalArrangement =
+                        Arrangement.spacedBy(space = 0.dp, alignment = Alignment.CenterVertically),
+                    modifier = Modifier.size(AlertScreenSize.dp).testTag(TEST_TAG),
+                )
+            }
         }
 
         val iconBottom = rule.onNodeWithTag(IconTestTag).getUnclippedBoundsInRoot().bottom
@@ -520,31 +566,33 @@
         val confirmButtonTop =
             rule.onNodeWithTag(ConfirmButtonTestTag).getUnclippedBoundsInRoot().top
 
-        titleTop.assertIsEqualTo(iconBottom + AlertDialogDefaults.iconBottomSpacing)
-        textTop.assertIsEqualTo(titleBottom + AlertDialogDefaults.textMessageTopSpacing)
-        confirmButtonTop.assertIsEqualTo(textBottom + AlertDialogDefaults.bottomSpacing)
+        titleTop.assertIsEqualTo(iconBottom + AlertIconBottomSpacing)
+        textTop.assertIsEqualTo(titleBottom + AlertTextMessageTopSpacing)
+        confirmButtonTop.assertIsEqualTo(textBottom + AlertBottomSpacing)
     }
 
     @Test
     fun with_icon_title_textMessage_content_confirmDismissButtons_positioning() {
         rule.setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
-            AlertDialog(
-                show = true,
-                icon = { TestImage(IconTestTag) },
-                title = { Text("Title", modifier = Modifier.testTag(TitleTestTag)) },
-                text = { Text("Text", modifier = Modifier.testTag(TextTestTag)) },
-                onDismissRequest = {},
-                confirmButton = {
-                    Button(onClick = {}, modifier = Modifier.testTag(ConfirmButtonTestTag)) {}
-                },
-                dismissButton = {
-                    Button(onClick = {}, modifier = Modifier.testTag(DismissButtonTestTag)) {}
-                },
-                verticalArrangement =
-                    Arrangement.spacedBy(space = 0.dp, alignment = Alignment.CenterVertically),
-                modifier = Modifier.testTag(TEST_TAG),
-            ) {
-                item { Text("ContentText", modifier = Modifier.testTag(ContentTestTag)) }
+            ScreenConfiguration(AlertScreenSize) {
+                AlertDialog(
+                    show = true,
+                    icon = { TestImage(IconTestTag) },
+                    title = { Text("Title", modifier = Modifier.testTag(TitleTestTag)) },
+                    text = { Text("Text", modifier = Modifier.testTag(TextTestTag)) },
+                    onDismissRequest = {},
+                    confirmButton = {
+                        Button(onClick = {}, modifier = Modifier.testTag(ConfirmButtonTestTag)) {}
+                    },
+                    dismissButton = {
+                        Button(onClick = {}, modifier = Modifier.testTag(DismissButtonTestTag)) {}
+                    },
+                    verticalArrangement =
+                        Arrangement.spacedBy(space = 0.dp, alignment = Alignment.CenterVertically),
+                    modifier = Modifier.size(AlertScreenSize.dp).testTag(TEST_TAG),
+                ) {
+                    item { Text("ContentText", modifier = Modifier.testTag(ContentTestTag)) }
+                }
             }
         }
 
@@ -558,31 +606,33 @@
         val confirmButtonTop =
             rule.onNodeWithTag(ConfirmButtonTestTag).getUnclippedBoundsInRoot().top
 
-        titleTop.assertIsEqualTo(iconBottom + AlertDialogDefaults.iconBottomSpacing)
-        textTop.assertIsEqualTo(titleBottom + AlertDialogDefaults.textMessageTopSpacing)
-        contentTop.assertIsEqualTo(textBottom + AlertDialogDefaults.textMessageTopSpacing)
-        confirmButtonTop.assertIsEqualTo(contentBottom + AlertDialogDefaults.bottomSpacing)
+        titleTop.assertIsEqualTo(iconBottom + AlertIconBottomSpacing)
+        textTop.assertIsEqualTo(titleBottom + AlertTextMessageTopSpacing)
+        contentTop.assertIsEqualTo(textBottom + AlertTextMessageTopSpacing)
+        confirmButtonTop.assertIsEqualTo(contentBottom + AlertBottomSpacing)
     }
 
     @Test
     fun with_icon_title_content_confirmDismissButtons_positioning() {
         rule.setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
-            AlertDialog(
-                show = true,
-                icon = { TestImage(IconTestTag) },
-                title = { Text("Title", modifier = Modifier.testTag(TitleTestTag)) },
-                onDismissRequest = {},
-                confirmButton = {
-                    Button(onClick = {}, modifier = Modifier.testTag(ConfirmButtonTestTag)) {}
-                },
-                dismissButton = {
-                    Button(onClick = {}, modifier = Modifier.testTag(DismissButtonTestTag)) {}
-                },
-                verticalArrangement =
-                    Arrangement.spacedBy(space = 0.dp, alignment = Alignment.CenterVertically),
-                modifier = Modifier.testTag(TEST_TAG),
-            ) {
-                item { Text("ContentText", modifier = Modifier.testTag(ContentTestTag)) }
+            ScreenConfiguration(AlertScreenSize) {
+                AlertDialog(
+                    show = true,
+                    icon = { TestImage(IconTestTag) },
+                    title = { Box(modifier = Modifier.size(3.dp).testTag(TitleTestTag)) },
+                    onDismissRequest = {},
+                    confirmButton = {
+                        Button(onClick = {}, modifier = Modifier.testTag(ConfirmButtonTestTag)) {}
+                    },
+                    dismissButton = {
+                        Button(onClick = {}, modifier = Modifier.testTag(DismissButtonTestTag)) {}
+                    },
+                    verticalArrangement =
+                        Arrangement.spacedBy(space = 0.dp, alignment = Alignment.CenterVertically),
+                    modifier = Modifier.size(AlertScreenSize.dp).testTag(TEST_TAG),
+                ) {
+                    item { Text("ContentText", modifier = Modifier.testTag(ContentTestTag)) }
+                }
             }
         }
 
@@ -594,9 +644,9 @@
         val confirmButtonTop =
             rule.onNodeWithTag(ConfirmButtonTestTag).getUnclippedBoundsInRoot().top
 
-        titleTop.assertIsEqualTo(iconBottom + AlertDialogDefaults.iconBottomSpacing)
-        contentTop.assertIsEqualTo(titleBottom + AlertDialogDefaults.textMessageTopSpacing)
-        confirmButtonTop.assertIsEqualTo(contentBottom + AlertDialogDefaults.bottomSpacing)
+        titleTop.assertIsEqualTo(iconBottom + AlertIconBottomSpacing)
+        contentTop.assertIsEqualTo(titleBottom + AlertTextMessageTopSpacing)
+        confirmButtonTop.assertIsEqualTo(contentBottom + AlertBottomSpacing)
     }
 
     // TODO: add more positioning tests for EdgeButton.
@@ -608,3 +658,5 @@
 private const val ContentTestTag = "content"
 private const val ConfirmButtonTestTag = "confirmButton"
 private const val DismissButtonTestTag = "dismissButton"
+private const val AlertScreenSize = 400
+private const val SmallScreenSize = 100
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/EdgeButtonScreenshotTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/EdgeButtonScreenshotTest.kt
index 7d9b092..7eada56 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/EdgeButtonScreenshotTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/EdgeButtonScreenshotTest.kt
@@ -135,7 +135,7 @@
             EdgeButton(
                 onClick = { /* Do something */ },
                 enabled = enabled,
-                buttonHeight = buttonHeight,
+                preferredHeight = buttonHeight,
                 modifier =
                     Modifier.align(Alignment.BottomEnd)
                         .testTag(TEST_TAG)
diff --git a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ProgressIndicatorTest.kt b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ProgressIndicatorTest.kt
index 2031ed4..ca94d41 100644
--- a/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ProgressIndicatorTest.kt
+++ b/wear/compose/compose-material3/src/androidTest/kotlin/androidx/wear/compose/material3/ProgressIndicatorTest.kt
@@ -77,7 +77,7 @@
     fun contains_progress_color() {
         setContentWithTheme {
             CircularProgressIndicator(
-                modifier = Modifier.size(SCREEN_SIZE_LARGE.dp).testTag(TEST_TAG),
+                modifier = Modifier.size(COMPONENT_SIZE).testTag(TEST_TAG),
                 progress = { 1f },
                 colors =
                     ProgressIndicatorDefaults.colors(
@@ -100,7 +100,7 @@
     fun contains_progress_incomplete_color() {
         setContentWithTheme {
             CircularProgressIndicator(
-                modifier = Modifier.size(SCREEN_SIZE_LARGE.dp).testTag(TEST_TAG),
+                modifier = Modifier.size(COMPONENT_SIZE).testTag(TEST_TAG),
                 progress = { 0f },
                 colors =
                     ProgressIndicatorDefaults.colors(
@@ -123,7 +123,7 @@
     fun change_start_end_angle() {
         setContentWithTheme {
             CircularProgressIndicator(
-                modifier = Modifier.size(SCREEN_SIZE_LARGE.dp).testTag(TEST_TAG),
+                modifier = Modifier.size(COMPONENT_SIZE).testTag(TEST_TAG),
                 progress = { 0.5f },
                 startAngle = 0f,
                 endAngle = 180f,
@@ -152,7 +152,7 @@
     fun set_small_progress_value() {
         setContentWithTheme {
             CircularProgressIndicator(
-                modifier = Modifier.size(SCREEN_SIZE_LARGE.dp).testTag(TEST_TAG),
+                modifier = Modifier.size(COMPONENT_SIZE).testTag(TEST_TAG),
                 progress = { 0.02f },
                 colors =
                     ProgressIndicatorDefaults.colors(
@@ -178,7 +178,7 @@
     fun set_small_stroke_width() {
         setContentWithTheme {
             CircularProgressIndicator(
-                modifier = Modifier.size(SCREEN_SIZE_LARGE.dp).testTag(TEST_TAG),
+                modifier = Modifier.size(COMPONENT_SIZE).testTag(TEST_TAG),
                 progress = { 0.5f },
                 strokeWidth = CircularProgressIndicatorDefaults.smallStrokeWidth,
                 colors =
@@ -204,7 +204,7 @@
     fun set_large_stroke_width() {
         setContentWithTheme {
             CircularProgressIndicator(
-                modifier = Modifier.size(SCREEN_SIZE_LARGE.dp).testTag(TEST_TAG),
+                modifier = Modifier.size(COMPONENT_SIZE).testTag(TEST_TAG),
                 progress = { 0.5f },
                 strokeWidth = CircularProgressIndicatorDefaults.largeStrokeWidth,
                 colors =
@@ -231,7 +231,7 @@
     fun progress_disabled_contains_disabled_colors() {
         setContentWithTheme {
             CircularProgressIndicator(
-                modifier = Modifier.testTag(TEST_TAG),
+                modifier = Modifier.size(COMPONENT_SIZE).testTag(TEST_TAG),
                 progress = { 0.5f },
                 enabled = false,
                 colors =
@@ -253,6 +253,10 @@
 
     private fun setContentWithTheme(composable: @Composable BoxScope.() -> Unit) {
         // Use constant size modifier to limit relative color percentage ranges.
-        rule.setContentWithTheme(modifier = Modifier.size(204.dp), composable = composable)
+        rule.setContentWithTheme(modifier = Modifier.size(COMPONENT_SIZE)) {
+            ScreenConfiguration(SCREEN_SIZE_LARGE) { composable() }
+        }
     }
 }
+
+private val COMPONENT_SIZE = 204.dp
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AlertDialog.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AlertDialog.kt
index 4f7238f..5fd2645 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AlertDialog.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/AlertDialog.kt
@@ -39,7 +39,6 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.rotate
 import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
@@ -47,13 +46,10 @@
 import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
 import androidx.wear.compose.foundation.lazy.ScalingLazyListScope
 import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
-import androidx.wear.compose.material3.AlertDialogDefaults.bottomSpacing
-import androidx.wear.compose.material3.AlertDialogDefaults.contentTopSpacing
-import androidx.wear.compose.material3.AlertDialogDefaults.iconBottomSpacing
-import androidx.wear.compose.material3.AlertDialogDefaults.textMessageTopSpacing
-import androidx.wear.compose.material3.AlertDialogDefaults.textPaddingFraction
-import androidx.wear.compose.material3.AlertDialogDefaults.titlePaddingFraction
+import androidx.wear.compose.material3.PaddingDefaults.horizontalContentPadding
+import androidx.wear.compose.material3.PaddingDefaults.verticalContentPadding
 import androidx.wear.compose.materialcore.isSmallScreen
+import androidx.wear.compose.materialcore.screenHeightDp
 import androidx.wear.compose.materialcore.screenWidthDp
 
 /**
@@ -72,14 +68,14 @@
  * @param onDismissRequest A lambda function to be called when the dialog is dismissed by swiping
  *   right (typically also called by the [dismissButton]).
  * @param confirmButton A slot for a [Button] indicating positive sentiment. Clicking the button
- *   must remove the dialog from the composition hierarchy. It's recommended to use
- *   [AlertDialogDefaults.ConfirmButton] in this slot with onClick callback.
+ *   must remove the dialog from the composition hierarchy e.g. by setting [show] to false. It's
+ *   recommended to use [AlertDialogDefaults.ConfirmButton] in this slot with onClick callback.
  * @param title A slot for displaying the title of the dialog. Title should contain a summary of the
  *   dialog's purpose or content and should not exceed 3 lines of text.
  * @param modifier Modifier to be applied to the dialog content.
  * @param dismissButton A slot for a [Button] indicating negative sentiment. Clicking the button
- *   must remove the dialog from the composition hierarchy. It's recommended to use
- *   [AlertDialogDefaults.DismissButton] in this slot with onClick callback.
+ *   must remove the dialog from the composition hierarchy e.g. by setting [show] to false. It's
+ *   recommended to use [AlertDialogDefaults.DismissButton] in this slot with onClick callback.
  * @param icon Optional slot for an icon to be shown at the top of the dialog.
  * @param text Optional slot for displaying the message of the dialog below the title. Should
  *   contain additional text that presents further details about the dialog's purpose if the title
@@ -104,7 +100,7 @@
     icon: @Composable (() -> Unit)? = null,
     text: @Composable (() -> Unit)? = null,
     verticalArrangement: Arrangement.Vertical = AlertDialogDefaults.VerticalArrangement,
-    contentPadding: PaddingValues = AlertDialogDefaults.contentPadding(hasBottomButton = false),
+    contentPadding: PaddingValues = AlertDialogDefaults.confirmDismissContentPadding(),
     properties: DialogProperties = DialogProperties(),
     content: (ScalingLazyListScope.() -> Unit)? = null
 ) {
@@ -142,9 +138,9 @@
  * @param show A boolean indicating whether the dialog should be displayed.
  * @param onDismissRequest A lambda function to be called when the dialog is dismissed by swiping to
  *   the right or by other dismiss action.
- * @param bottomButton A slot for a [EdgeButton] indicating positive sentiment. Clicking the button
- *   must remove the dialog from the composition hierarchy. It's recommended to use
- *   [AlertDialogDefaults.BottomButton] in this slot with onClick callback.
+ * @param bottomButton Optional slot for a [EdgeButton] indicating positive sentiment. Clicking the
+ *   button must remove the dialog from the composition hierarchy e.g. by setting [show] to false.
+ *   It's recommended to use [AlertDialogDefaults.BottomButton] in this slot with onClick callback.
  * @param title A slot for displaying the title of the dialog. Title should contain a summary of the
  *   dialog's purpose or content and should not exceed 3 lines of text.
  * @param modifier Modifier to be applied to the dialog content.
@@ -163,13 +159,13 @@
 fun AlertDialog(
     show: Boolean,
     onDismissRequest: () -> Unit,
-    bottomButton: @Composable BoxScope.() -> Unit,
+    bottomButton: (@Composable BoxScope.() -> Unit)?,
     title: @Composable () -> Unit,
     modifier: Modifier = Modifier,
     icon: @Composable (() -> Unit)? = null,
     text: @Composable (() -> Unit)? = null,
     verticalArrangement: Arrangement.Vertical = AlertDialogDefaults.VerticalArrangement,
-    contentPadding: PaddingValues = AlertDialogDefaults.contentPadding(hasBottomButton = true),
+    contentPadding: PaddingValues = AlertDialogDefaults.contentPadding(bottomButton != null),
     properties: DialogProperties = DialogProperties(),
     content: (ScalingLazyListScope.() -> Unit)? = null
 ) {
@@ -183,7 +179,9 @@
         title = title,
         icon = icon,
         text = text,
-        alertButtonsParams = AlertButtonsParams.BottomButton(bottomButton),
+        alertButtonsParams =
+            if (bottomButton != null) AlertButtonsParams.BottomButton(bottomButton)
+            else AlertButtonsParams.NoButtons,
         content = content
     )
 }
@@ -203,7 +201,7 @@
         EdgeButton(
             modifier = Modifier.padding(top = edgeButtonExtraTopPadding),
             onClick = onClick,
-            buttonHeight = ButtonDefaults.EdgeButtonHeightMedium,
+            preferredHeight = ButtonDefaults.EdgeButtonHeightMedium,
             content = content
         )
     }
@@ -258,23 +256,32 @@
     }
 
     /**
-     * The padding to apply around the content. Changes based on whether the dialog has a bottom
-     * button or not.
-     *
-     * @param hasBottomButton A boolean indicating whether the dialog has a bottom button.
+     * The padding to apply around the content for the [AlertDialog] variation with confirm dismiss
+     * buttons.
+     */
+    @Composable
+    fun confirmDismissContentPadding(): PaddingValues {
+        val verticalPadding = verticalContentPadding()
+        val horizontalPadding = horizontalContentPadding()
+        return PaddingValues(horizontal = horizontalPadding, vertical = verticalPadding)
+    }
+
+    /**
+     * The padding to apply around the content for the [AlertDialog] variation with a stack of
+     * options and optional bottom button.
      */
     @Composable
     fun contentPadding(hasBottomButton: Boolean): PaddingValues {
-        val screenWidth = LocalConfiguration.current.screenWidthDp
-        val verticalContentPadding =
-            screenWidth.dp * PaddingDefaults.verticalContentPaddingPercentage / 100
-        val horizontalContentPadding =
-            screenWidth.dp * PaddingDefaults.horizontalContentPaddingPercentage / 100
+        val topPadding = verticalContentPadding()
+        val horizontalPadding = horizontalContentPadding()
+        val bottomPadding =
+            if (hasBottomButton) edgeButtonHeightWithPadding
+            else screenHeightDp().dp * noEdgeButtonBottomPaddingFraction
         return PaddingValues(
-            top = verticalContentPadding,
-            bottom = if (hasBottomButton) edgeButtonHeightWithPadding else verticalContentPadding,
-            start = horizontalContentPadding,
-            end = horizontalContentPadding,
+            top = topPadding,
+            bottom = bottomPadding,
+            start = horizontalPadding,
+            end = horizontalPadding,
         )
     }
 
@@ -310,19 +317,10 @@
     }
 
     /** The extra top padding to apply to the edge button. */
-    val edgeButtonExtraTopPadding = 1.dp
-
-    internal val edgeButtonHeightWithPadding = ButtonDefaults.EdgeButtonHeightMedium + 7.dp
-
-    internal val titlePaddingFraction = 0.12f
-    internal val textPaddingFraction = 0.0416f
-
+    private val edgeButtonExtraTopPadding = 1.dp
+    private val edgeButtonHeightWithPadding = ButtonDefaults.EdgeButtonHeightMedium + 7.dp
+    internal val noEdgeButtonBottomPaddingFraction = 0.3646f
     internal val cancelButtonPadding = 1.dp
-    internal val iconBottomSpacing = 4.dp
-    internal val textMessageTopSpacing = 8.dp
-    internal val contentTopSpacing = 8.dp
-    internal val bottomSpacing = 8.dp
-    internal val titleMaxLines = 3
 }
 
 @Composable
@@ -370,7 +368,7 @@
                     item { TextMessage(text) }
                 }
                 if (content != null) {
-                    item { Spacer(Modifier.height(contentTopSpacing)) }
+                    item { Spacer(Modifier.height(ContentTopSpacing)) }
                     content()
                 }
 
@@ -379,8 +377,9 @@
                         item { ConfirmDismissButtons(alertButtonsParams) }
                     is AlertButtonsParams.BottomButton ->
                         if (content == null) {
-                            item { Spacer(Modifier.height(bottomSpacing)) }
+                            item { Spacer(Modifier.height(AlertBottomSpacing)) }
                         }
+                    is AlertButtonsParams.NoButtons -> Unit
                 }
             }
         }
@@ -391,13 +390,13 @@
 private fun IconAlert(content: @Composable () -> Unit) {
     Column {
         content()
-        Spacer(Modifier.height(iconBottomSpacing))
+        Spacer(Modifier.height(AlertIconBottomSpacing))
     }
 }
 
 @Composable
 private fun Title(content: @Composable () -> Unit) {
-    val horizontalPadding = screenWidthDp().dp * titlePaddingFraction
+    val horizontalPadding = screenWidthDp().dp * TitlePaddingFraction
     Column(modifier = Modifier.padding(horizontal = horizontalPadding)) {
         CompositionLocalProvider(
             LocalContentColor provides MaterialTheme.colorScheme.onBackground,
@@ -405,7 +404,7 @@
             LocalTextConfiguration provides
                 TextConfiguration(
                     textAlign = TextAlign.Center,
-                    maxLines = AlertDialogDefaults.titleMaxLines,
+                    maxLines = AlertTitleMaxLines,
                     overflow = TextOverflow.Ellipsis
                 ),
             content = content
@@ -416,7 +415,7 @@
 @Composable
 private fun ConfirmDismissButtons(alertButtonsParams: AlertButtonsParams.ConfirmDismissButtons) {
     Column {
-        Spacer(modifier = Modifier.height(bottomSpacing))
+        Spacer(modifier = Modifier.height(AlertBottomSpacing))
         Row(
             horizontalArrangement = Arrangement.Center,
             verticalAlignment = Alignment.CenterVertically
@@ -432,9 +431,9 @@
 
 @Composable
 private fun TextMessage(content: @Composable () -> Unit) {
-    val horizontalPadding = screenWidthDp().dp * textPaddingFraction
+    val horizontalPadding = screenWidthDp().dp * TextPaddingFraction
     Column(modifier = Modifier.padding(horizontal = horizontalPadding)) {
-        Spacer(Modifier.height(textMessageTopSpacing))
+        Spacer(Modifier.height(AlertTextMessageTopSpacing))
         CompositionLocalProvider(
             LocalContentColor provides MaterialTheme.colorScheme.onBackground,
             LocalTextStyle provides MaterialTheme.typography.bodyMedium,
@@ -450,12 +449,23 @@
 }
 
 private sealed interface AlertButtonsParams {
-    data class BottomButton(
+    object NoButtons : AlertButtonsParams
+
+    class BottomButton(
         val bottomButton: @Composable BoxScope.() -> Unit,
     ) : AlertButtonsParams
 
-    data class ConfirmDismissButtons(
+    class ConfirmDismissButtons(
         val confirmButton: @Composable RowScope.() -> Unit,
         val dismissButton: @Composable RowScope.() -> Unit
     ) : AlertButtonsParams
 }
+
+internal val AlertIconBottomSpacing = 4.dp
+internal val AlertTextMessageTopSpacing = 8.dp
+internal val AlertBottomSpacing = 8.dp
+internal const val AlertTitleMaxLines = 3
+
+private val ContentTopSpacing = 8.dp
+private const val TextPaddingFraction = 0.0416f
+private const val TitlePaddingFraction = 0.12f
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/DatePicker.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/DatePicker.kt
index 7764d08..b5e9a6c 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/DatePicker.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/DatePicker.kt
@@ -34,9 +34,6 @@
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
-import androidx.compose.material.icons.filled.Check
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.LaunchedEffect
@@ -66,6 +63,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.wear.compose.material3.ButtonDefaults.buttonColors
 import androidx.wear.compose.material3.ButtonDefaults.filledTonalButtonColors
+import androidx.wear.compose.material3.internal.Icons
 import androidx.wear.compose.material3.internal.Strings.Companion.DatePickerDay
 import androidx.wear.compose.material3.internal.Strings.Companion.DatePickerMonth
 import androidx.wear.compose.material3.internal.Strings.Companion.DatePickerYear
@@ -433,9 +431,9 @@
                 Icon(
                     imageVector =
                         if (showConfirm) {
-                            Icons.Filled.Check
+                            Icons.Check
                         } else {
-                            Icons.AutoMirrored.Filled.KeyboardArrowRight
+                            Icons.AutoMirrored.KeyboardArrowRight
                         },
                     contentDescription =
                         if (showConfirm) {
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/EdgeButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/EdgeButton.kt
index 469df92..c25b822 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/EdgeButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/EdgeButton.kt
@@ -107,7 +107,7 @@
  * @param modifier Modifier to be applied to the button. When animating the button to appear/
  *   disappear from the screen, a Modifier.height can be used to change the height of the component,
  *   but that won't change the space available for the content (though it may be scaled)
- * @param buttonHeight Defines the base size of the button, see the 4 standard sizes specified in
+ * @param preferredHeight Defines the base size of the button, see the 4 standard sizes specified in
  *   [ButtonDefaults]. This is used to determine the size constraints passed on to the content, and
  *   the size of the edge button if no height Modifier is specified. Note that if a height Modifier
  *   is specified for the EdgeButton, that will determine the size of the container, and the content
@@ -130,7 +130,7 @@
 fun EdgeButton(
     onClick: () -> Unit,
     modifier: Modifier = Modifier,
-    buttonHeight: Dp = ButtonDefaults.EdgeButtonHeightSmall,
+    preferredHeight: Dp = ButtonDefaults.EdgeButtonHeightSmall,
     enabled: Boolean = true,
     colors: ButtonColors = ButtonDefaults.buttonColors(),
     border: BorderStroke? = null,
@@ -143,10 +143,10 @@
     val screenWidthDp = screenWidthDp().dp
 
     val contentShapeHelper =
-        remember(buttonHeight) {
+        remember(preferredHeight) {
             ShapeHelper(density).apply {
                 // Compute the inner size using only the screen size and the buttonSize parameter
-                val size = with(density) { DpSize(screenWidthDp, buttonHeight).toSize() }
+                val size = with(density) { DpSize(screenWidthDp, preferredHeight).toSize() }
                 update(size)
             }
         }
@@ -178,7 +178,7 @@
                         } else {
                             screenWidthDp.roundToPx()
                         }
-                    val buttonHeightPx = with(density) { buttonHeight.roundToPx() }
+                    val buttonHeightPx = with(density) { preferredHeight.roundToPx() }
                     val size =
                         IntSize(
                             buttonWidthPx,
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/OpenOnPhoneDialog.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/OpenOnPhoneDialog.kt
index 6fccb24..fab1ed3 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/OpenOnPhoneDialog.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/OpenOnPhoneDialog.kt
@@ -84,7 +84,7 @@
  * @param durationMillis The duration in milliseconds for which the dialog is displayed. Defaults to
  *   [OpenOnPhoneDialogDefaults.DurationMillis].
  * @param content A slot for displaying an icon inside the open on phone dialog, which can be
- *   animated. Defaults to [OpenOnPhoneDialogDefaults.Icon].
+ *   animated. Defaults to [OpenOnPhoneDialogDefaults.OpenOnPhoneIcon].
  */
 @Composable
 fun OpenOnPhoneDialog(
@@ -95,7 +95,7 @@
     colors: OpenOnPhoneDialogColors = OpenOnPhoneDialogDefaults.colors(),
     properties: DialogProperties = DialogProperties(),
     durationMillis: Long = OpenOnPhoneDialogDefaults.DurationMillis,
-    content: @Composable BoxScope.() -> Unit = OpenOnPhoneDialogDefaults.Icon,
+    content: @Composable BoxScope.() -> Unit = OpenOnPhoneDialogDefaults.OpenOnPhoneIcon,
 ) {
     var progress by remember(show) { mutableFloatStateOf(0f) }
     val animatable = remember { Animatable(0f) }
@@ -160,7 +160,7 @@
      * animation.
      */
     @OptIn(ExperimentalAnimationGraphicsApi::class)
-    val Icon: @Composable BoxScope.() -> Unit = {
+    val OpenOnPhoneIcon: @Composable BoxScope.() -> Unit = {
         val animation =
             AnimatedImageVector.animatedVectorResource(R.drawable.wear_m3c_open_on_phone_animation)
         var atEnd by remember { mutableStateOf(false) }
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Padding.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Padding.kt
index 63f2325..89839e3 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Padding.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Padding.kt
@@ -16,6 +16,9 @@
 
 package androidx.wear.compose.material3
 
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 
 internal object PaddingDefaults {
@@ -27,11 +30,31 @@
     val verticalContentPaddingPercentage = 10f
 
     /**
+     * Vertical padding between top and bottom edges of the screen and the content for full screen
+     * components, as a dp.
+     */
+    @Composable
+    fun verticalContentPadding(): Dp {
+        val screenHeight = LocalConfiguration.current.screenHeightDp
+        return screenHeight.dp * verticalContentPaddingPercentage / 100
+    }
+
+    /**
      * Horizontal padding between start and end edges of the screen and the content for full screen
      * components, as a percentage.
      */
     val horizontalContentPaddingPercentage = 5.2f
 
+    /**
+     * Horizontal padding between start and end edges of the screen and the content for full screen
+     * components, as a dp.
+     */
+    @Composable
+    fun horizontalContentPadding(): Dp {
+        val screenWidth = LocalConfiguration.current.screenWidthDp
+        return screenWidth.dp * horizontalContentPaddingPercentage / 100
+    }
+
     /** Default minimum padding between the edge of the screen and the content. */
     val edgePadding = 2.dp
 }
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt
index 6326651..dd4ff33 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt
@@ -28,8 +28,6 @@
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.size
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Add
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.Immutable
@@ -52,6 +50,7 @@
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.wear.compose.material3.SliderDefaults.MaxSegmentSteps
+import androidx.wear.compose.material3.internal.Icons
 import androidx.wear.compose.material3.internal.Strings.Companion.SliderDecreaseButtonContentDescription
 import androidx.wear.compose.material3.internal.Strings.Companion.SliderIncreaseButtonContentDescription
 import androidx.wear.compose.material3.internal.getString
@@ -60,7 +59,6 @@
 import androidx.wear.compose.materialcore.InlineSliderButton
 import androidx.wear.compose.materialcore.RangeDefaults.calculateCurrentStepValue
 import androidx.wear.compose.materialcore.RangeDefaults.snapValueToStep
-import androidx.wear.compose.materialcore.RangeIcons
 import androidx.wear.compose.materialcore.directedValue
 import kotlin.math.roundToInt
 
@@ -228,8 +226,9 @@
  *
  * The bar in the middle of control can have separators if [segmented] flag is set to true. A number
  * of steps is calculated as the difference between max and min values of [valueProgression] divided
- * by [valueProgression].step - 1. For example, with a range of 100..120 and a step 5, number of
- * steps will be (120-100)/ 5 - 1 = 3. Steps are 100(first), 105, 110, 115, 120(last)
+ * by [valueProgression].step - 1. For example, with a range of 100..120 and a step 5, number of by
+ * [valueProgression].step - 1. For example, with a range of 100..120 and a step 5, number of steps
+ * will be (120-100)/ 5 - 1 = 3. Steps are 100(first), 105, 110, 115, 120(last)
  *
  * If [valueProgression] range is not equally divisible by [valueProgression].step, then
  * [valueProgression].last will be adjusted to the closest divisible value in the range. For
@@ -306,7 +305,7 @@
     @Composable
     fun DecreaseIcon(modifier: Modifier = Modifier) =
         Icon(
-            RangeIcons.Minus,
+            Icons.Remove,
             getString(SliderDecreaseButtonContentDescription),
             modifier.size(IconSize)
         )
@@ -314,11 +313,7 @@
     /** Increase Icon. */
     @Composable
     fun IncreaseIcon(modifier: Modifier = Modifier) =
-        Icon(
-            Icons.Filled.Add,
-            getString(SliderIncreaseButtonContentDescription),
-            modifier.size(IconSize)
-        )
+        Icon(Icons.Add, getString(SliderIncreaseButtonContentDescription), modifier.size(IconSize))
 
     /**
      * Creates a [SliderColors] that represents the default background and content colors used in an
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimePicker.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimePicker.kt
index cffdb96..cb1daac 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimePicker.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TimePicker.kt
@@ -33,8 +33,6 @@
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Check
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.LaunchedEffect
@@ -63,6 +61,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.max
 import androidx.wear.compose.material3.ButtonDefaults.buttonColors
+import androidx.wear.compose.material3.internal.Icons
 import androidx.wear.compose.material3.internal.Plurals
 import androidx.wear.compose.material3.internal.Strings
 import androidx.wear.compose.material3.internal.getPlurals
@@ -333,7 +332,7 @@
                         }
                         .focusRequester(focusRequesterConfirmButton)
                         .focusable(),
-                buttonHeight = ButtonDefaults.EdgeButtonHeightSmall,
+                preferredHeight = ButtonDefaults.EdgeButtonHeightSmall,
                 colors =
                     buttonColors(
                         contentColor = colors.confirmButtonContentColor,
@@ -341,7 +340,7 @@
                     ),
             ) {
                 Icon(
-                    imageVector = Icons.Filled.Check,
+                    imageVector = Icons.Check,
                     contentDescription = getString(Strings.PickerConfirmButtonContentDescription),
                     modifier = Modifier.size(24.dp).wrapContentSize(align = Alignment.Center),
                 )
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Icons.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Icons.kt
new file mode 100644
index 0000000..8825d23
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/internal/Icons.kt
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3.internal
+
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.graphics.vector.PathBuilder
+import androidx.compose.ui.graphics.vector.path
+import androidx.compose.ui.unit.dp
+
+internal object Icons {
+    internal val Add: ImageVector
+        get() {
+            if (_add != null) {
+                return _add!!
+            }
+            _add =
+                materialIcon(name = "Add") {
+                    materialPath {
+                        moveTo(440f, 520f)
+                        lineTo(240f, 520f)
+                        quadTo(223f, 520f, 211.5f, 508.5f)
+                        quadTo(200f, 497f, 200f, 480f)
+                        quadTo(200f, 463f, 211.5f, 451.5f)
+                        quadTo(223f, 440f, 240f, 440f)
+                        lineTo(440f, 440f)
+                        lineTo(440f, 240f)
+                        quadTo(440f, 223f, 451.5f, 211.5f)
+                        quadTo(463f, 200f, 480f, 200f)
+                        quadTo(497f, 200f, 508.5f, 211.5f)
+                        quadTo(520f, 223f, 520f, 240f)
+                        lineTo(520f, 440f)
+                        lineTo(720f, 440f)
+                        quadTo(737f, 440f, 748.5f, 451.5f)
+                        quadTo(760f, 463f, 760f, 480f)
+                        quadTo(760f, 497f, 748.5f, 508.5f)
+                        quadTo(737f, 520f, 720f, 520f)
+                        lineTo(520f, 520f)
+                        lineTo(520f, 720f)
+                        quadTo(520f, 737f, 508.5f, 748.5f)
+                        quadTo(497f, 760f, 480f, 760f)
+                        quadTo(463f, 760f, 451.5f, 748.5f)
+                        quadTo(440f, 737f, 440f, 720f)
+                        lineTo(440f, 520f)
+                        close()
+                    }
+                }
+            return _add!!
+        }
+
+    private var _add: ImageVector? = null
+
+    internal val Remove: ImageVector
+        get() {
+            if (_remove != null) {
+                return _remove!!
+            }
+            _remove =
+                materialIcon(name = "Remove") {
+                    materialPath {
+                        moveTo(240f, 520f)
+                        quadTo(223f, 520f, 211.5f, 508.5f)
+                        quadTo(200f, 497f, 200f, 480f)
+                        quadTo(200f, 463f, 211.5f, 451.5f)
+                        quadTo(223f, 440f, 240f, 440f)
+                        lineTo(720f, 440f)
+                        quadTo(737f, 440f, 748.5f, 451.5f)
+                        quadTo(760f, 463f, 760f, 480f)
+                        quadTo(760f, 497f, 748.5f, 508.5f)
+                        quadTo(737f, 520f, 720f, 520f)
+                        lineTo(240f, 520f)
+                        close()
+                    }
+                }
+            return _remove!!
+        }
+
+    private var _remove: ImageVector? = null
+
+    internal val Check: ImageVector
+        get() {
+            if (_check != null) {
+                return _check!!
+            }
+            _check =
+                materialIcon(name = "Check") {
+                    materialPath {
+                        moveTo(382f, 597.87f)
+                        lineTo(716.7f, 263.17f)
+                        quadTo(730.37f, 249.5f, 748.76f, 249.5f)
+                        quadTo(767.15f, 249.5f, 780.83f, 263.17f)
+                        quadTo(794.5f, 276.85f, 794.5f, 295.62f)
+                        quadTo(794.5f, 314.39f, 780.83f, 328.07f)
+                        lineTo(414.07f, 695.59f)
+                        quadTo(400.39f, 709.26f, 382f, 709.26f)
+                        quadTo(363.61f, 709.26f, 349.93f, 695.59f)
+                        lineTo(178.41f, 524.07f)
+                        quadTo(164.74f, 510.39f, 165.12f, 491.62f)
+                        quadTo(165.5f, 472.85f, 179.17f, 459.17f)
+                        quadTo(192.85f, 445.5f, 211.62f, 445.5f)
+                        quadTo(230.39f, 445.5f, 244.07f, 459.17f)
+                        lineTo(382f, 597.87f)
+                        close()
+                    }
+                }
+            return _check!!
+        }
+
+    private var _check: ImageVector? = null
+
+    internal object AutoMirrored {
+        internal val KeyboardArrowRight: ImageVector
+            get() {
+                if (_keyboardArrowRight != null) {
+                    return _keyboardArrowRight!!
+                }
+                _keyboardArrowRight =
+                    materialIcon(
+                        name = "AutoMirrored.KeyboardArrowRight",
+                        autoMirror = true,
+                    ) {
+                        materialPath {
+                            moveTo(496.35f, 480f)
+                            lineTo(344.17f, 327.83f)
+                            quadTo(331.5f, 315.15f, 331.5f, 296f)
+                            quadTo(331.5f, 276.85f, 344.17f, 264.17f)
+                            quadTo(356.85f, 251.5f, 376f, 251.5f)
+                            quadTo(395.15f, 251.5f, 407.83f, 264.17f)
+                            lineTo(591.59f, 447.93f)
+                            quadTo(598.3f, 454.65f, 601.4f, 462.85f)
+                            quadTo(604.5f, 471.04f, 604.5f, 480f)
+                            quadTo(604.5f, 488.96f, 601.4f, 497.15f)
+                            quadTo(598.3f, 505.35f, 591.59f, 512.07f)
+                            lineTo(407.83f, 695.83f)
+                            quadTo(395.15f, 708.5f, 376f, 708.5f)
+                            quadTo(356.85f, 708.5f, 344.17f, 695.83f)
+                            quadTo(331.5f, 683.15f, 331.5f, 664f)
+                            quadTo(331.5f, 644.85f, 344.17f, 632.17f)
+                            lineTo(496.35f, 480f)
+                            close()
+                        }
+                    }
+                return _keyboardArrowRight!!
+            }
+
+        private var _keyboardArrowRight: ImageVector? = null
+    }
+}
+
+private inline fun materialIcon(
+    name: String,
+    autoMirror: Boolean = false,
+    block: ImageVector.Builder.() -> ImageVector.Builder
+): ImageVector =
+    ImageVector.Builder(
+            name = name,
+            defaultWidth = MaterialIconDimension,
+            defaultHeight = MaterialIconDimension,
+            viewportWidth = MaterialIconViewPointDimension,
+            viewportHeight = MaterialIconViewPointDimension,
+            autoMirror = autoMirror
+        )
+        .block()
+        .build()
+
+private inline fun ImageVector.Builder.materialPath(pathBuilder: PathBuilder.() -> Unit) =
+    path(fill = SolidColor(Color.White), pathBuilder = pathBuilder)
+
+private val MaterialIconDimension = 24.dp
+private const val MaterialIconViewPointDimension = 960f
diff --git a/wear/compose/compose-material3/src/main/res/values-af/strings.xml b/wear/compose/compose-material3/src/main/res/values-af/strings.xml
index 292ca6d..05c0230 100644
--- a/wear/compose/compose-material3/src/main/res/values-af/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-af/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Jaar"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Bevestig"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Volgende"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Verminder"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Vermeerder"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Het misluk"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Sukses"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Maak op foon oop"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-am/strings.xml b/wear/compose/compose-material3/src/main/res/values-am/strings.xml
index 1f165fd..e9ff4a7 100644
--- a/wear/compose/compose-material3/src/main/res/values-am/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-am/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"ዓመት"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"አረጋግጥ"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"ቀጣይ"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"ቀንስ"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"ጨምር"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"አልተሳካም"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"ተሳክቷል"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"ስልክ ላይ ክፈት"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ar/strings.xml b/wear/compose/compose-material3/src/main/res/values-ar/strings.xml
index b39dc3c6..53273dd 100644
--- a/wear/compose/compose-material3/src/main/res/values-ar/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ar/strings.xml
@@ -50,10 +50,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"السنة"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"تأكيد"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"التالي"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"تقليل"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"زيادة"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"تعذر الإجراء"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"نجحَ الإجراء"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"فتح على الهاتف"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-b+sr+Latn/strings.xml b/wear/compose/compose-material3/src/main/res/values-b+sr+Latn/strings.xml
index 88e265dc..4e4afdb 100644
--- a/wear/compose/compose-material3/src/main/res/values-b+sr+Latn/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-b+sr+Latn/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Godina"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Potvrdi"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Dalje"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Smanji"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Povećaj"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Nije uspelo"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Uspelo"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Na telefonu"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-be/strings.xml b/wear/compose/compose-material3/src/main/res/values-be/strings.xml
index dd3df95..4e4b539 100644
--- a/wear/compose/compose-material3/src/main/res/values-be/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-be/strings.xml
@@ -44,10 +44,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Год"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Пацвердзіць"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Далей"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Паменшыць"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Павялічыць"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Памылка"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Выканана"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"На тэлефоне"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-bg/strings.xml b/wear/compose/compose-material3/src/main/res/values-bg/strings.xml
index df9c2d1..c4df263 100644
--- a/wear/compose/compose-material3/src/main/res/values-bg/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-bg/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Година"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Потвърждаване"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Напред"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Намаляване"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Увеличаване"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Неуспешно"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Успешно"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Отв. на тел."</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-bs/strings.xml b/wear/compose/compose-material3/src/main/res/values-bs/strings.xml
index 82bc2f2..d963380 100644
--- a/wear/compose/compose-material3/src/main/res/values-bs/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-bs/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Godina"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Potvrđivanje"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Naprijed"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Smanjivanje"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Povećavanje"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Neuspješno"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Uspješno"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Otvor. na tel."</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ca/strings.xml b/wear/compose/compose-material3/src/main/res/values-ca/strings.xml
index 1a58fc7fa..9f2ef3e 100644
--- a/wear/compose/compose-material3/src/main/res/values-ca/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ca/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Any"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirma"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Següent"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Redueix"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Augmenta"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Ha fallat"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Correcte"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Obre al telèfon"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-cs/strings.xml b/wear/compose/compose-material3/src/main/res/values-cs/strings.xml
index 63526fc..2265857 100644
--- a/wear/compose/compose-material3/src/main/res/values-cs/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-cs/strings.xml
@@ -44,10 +44,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Rok"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Potvrdit"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Další"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Snížit"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Zvýšit"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Nezdařilo se"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Hotovo"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Otevřít v telefonu"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-da/strings.xml b/wear/compose/compose-material3/src/main/res/values-da/strings.xml
index fc69382..874dff6 100644
--- a/wear/compose/compose-material3/src/main/res/values-da/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-da/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"År"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Bekræft"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Næste"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Dæmp"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Øg"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Mislykket"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Gennemført"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Åbn på telefon"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-el/strings.xml b/wear/compose/compose-material3/src/main/res/values-el/strings.xml
index 625ca9a..9f594ae 100644
--- a/wear/compose/compose-material3/src/main/res/values-el/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-el/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Έτος"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Επιβεβαίωση"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Επόμενο"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Μείωση"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Αύξηση"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Αποτυχία"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Επιτυχία"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Στο τηλέφωνο"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-en-rAU/strings.xml b/wear/compose/compose-material3/src/main/res/values-en-rAU/strings.xml
index 36aabdf..bdc4036 100644
--- a/wear/compose/compose-material3/src/main/res/values-en-rAU/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-en-rAU/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Year"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirm"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Next"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Decrease"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Increase"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Failed"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Success"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Open on phone"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-en-rGB/strings.xml b/wear/compose/compose-material3/src/main/res/values-en-rGB/strings.xml
index 36aabdf..bdc4036 100644
--- a/wear/compose/compose-material3/src/main/res/values-en-rGB/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-en-rGB/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Year"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirm"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Next"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Decrease"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Increase"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Failed"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Success"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Open on phone"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-en-rIN/strings.xml b/wear/compose/compose-material3/src/main/res/values-en-rIN/strings.xml
index 36aabdf..bdc4036 100644
--- a/wear/compose/compose-material3/src/main/res/values-en-rIN/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-en-rIN/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Year"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirm"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Next"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Decrease"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Increase"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Failed"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Success"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Open on phone"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-es/strings.xml b/wear/compose/compose-material3/src/main/res/values-es/strings.xml
index ec62941..1502faf 100644
--- a/wear/compose/compose-material3/src/main/res/values-es/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-es/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Año"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirmar"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Siguiente"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Reducir"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Aumentar"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Error"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Todo correcto"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Ábrelo en el teléfono"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-fa/strings.xml b/wear/compose/compose-material3/src/main/res/values-fa/strings.xml
index 4ed7b8a..e92b365 100644
--- a/wear/compose/compose-material3/src/main/res/values-fa/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-fa/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"سال"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"تأیید کردن"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"بعدی"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"کاهش دادن"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"افزایش دادن"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"انجام نشد"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"انجام شد"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"باز کردن در تلفن"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-fi/strings.xml b/wear/compose/compose-material3/src/main/res/values-fi/strings.xml
index 04460eb..6759232 100644
--- a/wear/compose/compose-material3/src/main/res/values-fi/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-fi/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Vuosi"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Vahvista"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Seuraava"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Vähennä"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Lisää"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Epäonnistui"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Onnistui"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Puhelimella"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-fr-rCA/strings.xml b/wear/compose/compose-material3/src/main/res/values-fr-rCA/strings.xml
index b3e47a1..a1fbf8e 100644
--- a/wear/compose/compose-material3/src/main/res/values-fr-rCA/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-fr-rCA/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Année"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirmer"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Suivant"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Diminuer"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Augmenter"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Échec"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Réussite"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Ouv. ds tél."</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-fr/strings.xml b/wear/compose/compose-material3/src/main/res/values-fr/strings.xml
index df054a0..7af8ee6 100644
--- a/wear/compose/compose-material3/src/main/res/values-fr/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-fr/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Année"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirmer"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Suivant"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Diminuer"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Augmenter"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Échec"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Opération réussie"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Ouvrir sur le téléphone"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-hr/strings.xml b/wear/compose/compose-material3/src/main/res/values-hr/strings.xml
index f36d824..d2e3e8e 100644
--- a/wear/compose/compose-material3/src/main/res/values-hr/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-hr/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Godina"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Potvrdi"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Dalje"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Smanji"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Povećaj"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Nije uspjelo"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Uspjeh"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Na telefonu"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-hu/strings.xml b/wear/compose/compose-material3/src/main/res/values-hu/strings.xml
index fd95148..9fb9621 100644
--- a/wear/compose/compose-material3/src/main/res/values-hu/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-hu/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Év"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Megerősítés"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Következő"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Csökkentés"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Növelés"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Sikertelen"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Sikerült"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Nyissa meg mobilon"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-hy/strings.xml b/wear/compose/compose-material3/src/main/res/values-hy/strings.xml
index 7b98efb..453d5f5 100644
--- a/wear/compose/compose-material3/src/main/res/values-hy/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-hy/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Տարի"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Հաստատել"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Հաջորդը"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Նվազեցնել"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Ավելացնել"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Ձախողվել է"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Պատրաստ է"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Բացեք հեռախոսում"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-in/strings.xml b/wear/compose/compose-material3/src/main/res/values-in/strings.xml
index b41351a..f4f1f2c 100644
--- a/wear/compose/compose-material3/src/main/res/values-in/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-in/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Tahun"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Konfirmasi"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Berikutnya"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Turunkan"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Tingkatkan"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Gagal"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Berhasil"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Buka di ponsel"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-it/strings.xml b/wear/compose/compose-material3/src/main/res/values-it/strings.xml
index 3a0f0b5..5b48a38 100644
--- a/wear/compose/compose-material3/src/main/res/values-it/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-it/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Anno"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Conferma"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Avanti"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Diminuisci"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Aumenta"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Non riuscita"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Riuscita"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Su smartph."</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-iw/strings.xml b/wear/compose/compose-material3/src/main/res/values-iw/strings.xml
index d0a9318..239c255 100644
--- a/wear/compose/compose-material3/src/main/res/values-iw/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-iw/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"שנה"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"אישור"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"הבא"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"הפחתה"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"הגברה"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"הפעולה נכשלה"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"הפעולה הצליחה"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"פתיחה בטלפון"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ja/strings.xml b/wear/compose/compose-material3/src/main/res/values-ja/strings.xml
index 1021184..8cbdd56 100644
--- a/wear/compose/compose-material3/src/main/res/values-ja/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ja/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"年"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"確認"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"次へ"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"下げる"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"上げる"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"失敗"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"成功"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"スマホで開く"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ka/strings.xml b/wear/compose/compose-material3/src/main/res/values-ka/strings.xml
index ac02463..ed13a53 100644
--- a/wear/compose/compose-material3/src/main/res/values-ka/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ka/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"წელი"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"დადასტურება"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"შემდეგი"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"შემცირება"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"გაზრდა"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"ვერ შესრულდა"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"შესრულდა"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"ტელეფონში გახსნა"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-kk/strings.xml b/wear/compose/compose-material3/src/main/res/values-kk/strings.xml
index 05e8014..4ada781 100644
--- a/wear/compose/compose-material3/src/main/res/values-kk/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-kk/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Жыл"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Растау"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Келесі"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Азайту"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Көбейту"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Расталмады."</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Расталды."</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Телефоннан ашыңыз."</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ko/strings.xml b/wear/compose/compose-material3/src/main/res/values-ko/strings.xml
index 1643534..1710e88 100644
--- a/wear/compose/compose-material3/src/main/res/values-ko/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ko/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"년"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"확인"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"다음"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"감소"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"증가"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"실패"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"성공"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"휴대전화에서 열기"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ky/strings.xml b/wear/compose/compose-material3/src/main/res/values-ky/strings.xml
index c312a62..3a9abba 100644
--- a/wear/compose/compose-material3/src/main/res/values-ky/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ky/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Жыл"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Ырастоо"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Кийинки"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Төмөндөтүү"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Жогорулатуу"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Ишке ашпады"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Ийгилик"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Телефондо ачуу"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-lt/strings.xml b/wear/compose/compose-material3/src/main/res/values-lt/strings.xml
index 8397c2a..474becb 100644
--- a/wear/compose/compose-material3/src/main/res/values-lt/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-lt/strings.xml
@@ -44,10 +44,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Metai"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Patvirtinti"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Kitas"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Sumažinti"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Padidinti"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Nepavyko"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Pavyko"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Atidaryti telefone"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-lv/strings.xml b/wear/compose/compose-material3/src/main/res/values-lv/strings.xml
index c440c8b..fc3777f 100644
--- a/wear/compose/compose-material3/src/main/res/values-lv/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-lv/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Gads"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Apstiprināt"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Tālāk"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Samazināt"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Palielināt"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Neizdevās"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Izdevās"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Atvērt tālrunī"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-my/strings.xml b/wear/compose/compose-material3/src/main/res/values-my/strings.xml
index c0393c2..1c2fc6e1 100644
--- a/wear/compose/compose-material3/src/main/res/values-my/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-my/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"နှစ်"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"အတည်ပြုရန်"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"ရှေ့သို့"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"လျှော့ရန်"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"တိုးရန်"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"မအောင်မြင်ပါ"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"အောင်မြင်သည်"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"ဖုန်း၌ဖွင့်ရန်"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-nb/strings.xml b/wear/compose/compose-material3/src/main/res/values-nb/strings.xml
index d827ac5..6e3d0e6 100644
--- a/wear/compose/compose-material3/src/main/res/values-nb/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-nb/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"År"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Bekreft"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Neste"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Reduser"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Øk"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Mislyktes"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Vellykket"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Åpne på tlf."</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-nl/strings.xml b/wear/compose/compose-material3/src/main/res/values-nl/strings.xml
index d89b6e9..a72911c 100644
--- a/wear/compose/compose-material3/src/main/res/values-nl/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-nl/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Jaar"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Bevestigen"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Volgende"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Verlagen"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Verhogen"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Mislukt"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Geslaagd"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Openen op telefoon"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-pl/strings.xml b/wear/compose/compose-material3/src/main/res/values-pl/strings.xml
index 5484b3e..b958497 100644
--- a/wear/compose/compose-material3/src/main/res/values-pl/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-pl/strings.xml
@@ -44,10 +44,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Rok"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Potwierdź"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Dalej"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Zmniejsz"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Zwiększ"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Niepowodzenie"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Udało się"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Otwórz na telefonie"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-pt-rBR/strings.xml b/wear/compose/compose-material3/src/main/res/values-pt-rBR/strings.xml
index 12c2766..41b3ee7 100644
--- a/wear/compose/compose-material3/src/main/res/values-pt-rBR/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-pt-rBR/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Ano"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirmar"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Avançar"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Diminuir"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Aumentar"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Falha"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Pronto"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Abra no smartphone"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-pt-rPT/strings.xml b/wear/compose/compose-material3/src/main/res/values-pt-rPT/strings.xml
index c4d5152..3370f8b 100644
--- a/wear/compose/compose-material3/src/main/res/values-pt-rPT/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-pt-rPT/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Ano"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirmar"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Seguinte"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Diminuir"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Aumentar"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Falhou"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Concluído"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Abrir no tel."</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-pt/strings.xml b/wear/compose/compose-material3/src/main/res/values-pt/strings.xml
index 12c2766..41b3ee7 100644
--- a/wear/compose/compose-material3/src/main/res/values-pt/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-pt/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Ano"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirmar"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Avançar"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Diminuir"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Aumentar"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Falha"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Pronto"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Abra no smartphone"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ro/strings.xml b/wear/compose/compose-material3/src/main/res/values-ro/strings.xml
index 03c63d69..10fbc69 100644
--- a/wear/compose/compose-material3/src/main/res/values-ro/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ro/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"An"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Confirmă"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Înainte"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Redu"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Crește"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Eroare"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Succes"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Pe telefon"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ru/strings.xml b/wear/compose/compose-material3/src/main/res/values-ru/strings.xml
index 84d7feb..2cffbc8 100644
--- a/wear/compose/compose-material3/src/main/res/values-ru/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ru/strings.xml
@@ -44,10 +44,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Год"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Подтвердить"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Далее"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Уменьшить"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Увеличить"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Ошибка"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Готово"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Открыть на телефоне"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-sk/strings.xml b/wear/compose/compose-material3/src/main/res/values-sk/strings.xml
index 0b391a8..4922721 100644
--- a/wear/compose/compose-material3/src/main/res/values-sk/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-sk/strings.xml
@@ -44,10 +44,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Rok"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Potvrdiť"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Ďalej"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Znížiť"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Zvýšiť"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Neúspešné"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Podarilo sa"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Otvorte v telefóne"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-sr/strings.xml b/wear/compose/compose-material3/src/main/res/values-sr/strings.xml
index c5bdeb3..07713ae 100644
--- a/wear/compose/compose-material3/src/main/res/values-sr/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-sr/strings.xml
@@ -41,10 +41,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Година"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Потврди"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Даље"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Смањи"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Повећај"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Није успело"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Успело"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"На телефону"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-sv/strings.xml b/wear/compose/compose-material3/src/main/res/values-sv/strings.xml
index 8e24d46..a4c4e6a 100644
--- a/wear/compose/compose-material3/src/main/res/values-sv/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-sv/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"År"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Bekräfta"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Nästa"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Minska"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Öka"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Misslyckades"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Klart"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"På telefonen"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-sw/strings.xml b/wear/compose/compose-material3/src/main/res/values-sw/strings.xml
index c2ad8cb..942c959 100644
--- a/wear/compose/compose-material3/src/main/res/values-sw/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-sw/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Mwaka"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Thibitisha"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Endelea"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Punguza"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Ongeza"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Imeshindwa"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Imemaliza"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Fungua kwenye simu"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-ta/strings.xml b/wear/compose/compose-material3/src/main/res/values-ta/strings.xml
index 4f93096..fd278fc 100644
--- a/wear/compose/compose-material3/src/main/res/values-ta/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-ta/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"ஆண்டு"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"உறுதிசெய்யும்"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"அடுத்ததற்குச் செல்லும்"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"குறைக்கும்"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"அதிகரிக்கும்"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"தோல்வி"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"முடிந்தது"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"மொபைலில் திற"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-th/strings.xml b/wear/compose/compose-material3/src/main/res/values-th/strings.xml
index d4825e4..8576aa4 100644
--- a/wear/compose/compose-material3/src/main/res/values-th/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-th/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"ปี"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"ยืนยัน"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"ถัดไป"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"ลด"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"เพิ่ม"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"ไม่สำเร็จ"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"สำเร็จ"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"เปิดในโทรศัพท์"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-tl/strings.xml b/wear/compose/compose-material3/src/main/res/values-tl/strings.xml
index 186c0ed..f8cf9bf7a 100644
--- a/wear/compose/compose-material3/src/main/res/values-tl/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-tl/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Taon"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Kumpirmahin"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Susunod"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Bawasan"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Dagdagan"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Nabigo"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Matagumpay"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Buksan"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-tr/strings.xml b/wear/compose/compose-material3/src/main/res/values-tr/strings.xml
index c978fe0..a8a32e6 100644
--- a/wear/compose/compose-material3/src/main/res/values-tr/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-tr/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Yıl"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Onayla"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Sonraki"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Azalt"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Artır"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Başarısız"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Başarılı"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Telefonda aç"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-uk/strings.xml b/wear/compose/compose-material3/src/main/res/values-uk/strings.xml
index 3fa6994..0c45993 100644
--- a/wear/compose/compose-material3/src/main/res/values-uk/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-uk/strings.xml
@@ -44,10 +44,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Рік"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Підтвердити"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Далі"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Зменшити"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Збільшити"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Помилка"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Готово"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"На телефоні"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-uz/strings.xml b/wear/compose/compose-material3/src/main/res/values-uz/strings.xml
index d99a721..2d2f2d3 100644
--- a/wear/compose/compose-material3/src/main/res/values-uz/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-uz/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Yil"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Tasdiqlash"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Keyingisi"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Pasaytirish"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Oshirish"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Bajarilmadi"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Bajarildi"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Telefonda"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-vi/strings.xml b/wear/compose/compose-material3/src/main/res/values-vi/strings.xml
index 65f5eb1..093ccf7 100644
--- a/wear/compose/compose-material3/src/main/res/values-vi/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-vi/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Năm"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Xác nhận"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Tiếp theo"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Giảm"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Tăng"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Lỗi"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Thành công"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Mở trên điện thoại"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-zh-rCN/strings.xml b/wear/compose/compose-material3/src/main/res/values-zh-rCN/strings.xml
index c395e20..81d4b75 100644
--- a/wear/compose/compose-material3/src/main/res/values-zh-rCN/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-zh-rCN/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"年"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"确认"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"下一个"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"降低"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"增加"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"失败"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"成功"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"在手机上打开"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-zh-rHK/strings.xml b/wear/compose/compose-material3/src/main/res/values-zh-rHK/strings.xml
index 7306379..64d4bdb 100644
--- a/wear/compose/compose-material3/src/main/res/values-zh-rHK/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-zh-rHK/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"年"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"確認"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"下一步"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"調低"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"調高"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"失敗"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"成功"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"在手機開啟"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-zh-rTW/strings.xml b/wear/compose/compose-material3/src/main/res/values-zh-rTW/strings.xml
index dfe78d7..ef57e4c 100644
--- a/wear/compose/compose-material3/src/main/res/values-zh-rTW/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-zh-rTW/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"年"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"確認"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"下一個"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"調低"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"調高"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"失敗"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"成功"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"在手機上開啟"</string>
diff --git a/wear/compose/compose-material3/src/main/res/values-zu/strings.xml b/wear/compose/compose-material3/src/main/res/values-zu/strings.xml
index 259ef34..c14db45 100644
--- a/wear/compose/compose-material3/src/main/res/values-zu/strings.xml
+++ b/wear/compose/compose-material3/src/main/res/values-zu/strings.xml
@@ -38,10 +38,8 @@
     <string name="wear_m3c_date_picker_year" msgid="4697690064312147449">"Unyaka"</string>
     <string name="wear_m3c_picker_confirm_button_content_description" msgid="8440144719909288057">"Qinisekisa"</string>
     <string name="wear_m3c_picker_next_button_content_description" msgid="3346011303652897029">"Okulandelayo"</string>
-    <!-- no translation found for wear_m3c_slider_decrease_content_description (8242572466064289486) -->
-    <skip />
-    <!-- no translation found for wear_m3c_slider_increase_content_description (3329631766954416834) -->
-    <skip />
+    <string name="wear_m3c_slider_decrease_content_description" msgid="8242572466064289486">"Yehlisa"</string>
+    <string name="wear_m3c_slider_increase_content_description" msgid="3329631766954416834">"Khulisa"</string>
     <string name="wear_m3c_confirmation_failure_message" msgid="6690105830380889949">"Yehlulekile"</string>
     <string name="wear_m3c_confirmation_success_message" msgid="7045594078216655038">"Impumelelo"</string>
     <string name="wear_m3c_open_on_phone" msgid="5829463187924353601">"Vula efonini"</string>
diff --git a/wear/protolayout/protolayout-lint/src/main/java/androidx/wear/protolayout/lint/ProtoLayoutIssueRegistry.kt b/wear/protolayout/protolayout-lint/src/main/java/androidx/wear/protolayout/lint/ProtoLayoutIssueRegistry.kt
index 44e2353..e9be99b 100644
--- a/wear/protolayout/protolayout-lint/src/main/java/androidx/wear/protolayout/lint/ProtoLayoutIssueRegistry.kt
+++ b/wear/protolayout/protolayout-lint/src/main/java/androidx/wear/protolayout/lint/ProtoLayoutIssueRegistry.kt
@@ -23,7 +23,7 @@
 /** Issue Registry containing ProtoLayout specific lint Issues. */
 @Suppress("UnstableApiUsage")
 class ProtoLayoutIssueRegistry : IssueRegistry() {
-    override val api = 14
+    override val api = 16
     override val minApi = CURRENT_API
     override val issues =
         listOf(
diff --git a/wear/protolayout/protolayout-lint/src/test/java/ApiLintVersionsTest.kt b/wear/protolayout/protolayout-lint/src/test/java/ApiLintVersionsTest.kt
index 23ce189..a2ab809 100644
--- a/wear/protolayout/protolayout-lint/src/test/java/ApiLintVersionsTest.kt
+++ b/wear/protolayout/protolayout-lint/src/test/java/ApiLintVersionsTest.kt
@@ -32,6 +32,6 @@
 
         val registry = ProtoLayoutIssueRegistry()
         assertThat(registry.api).isEqualTo(CURRENT_API)
-        assertThat(registry.minApi).isEqualTo(14)
+        assertThat(registry.minApi).isEqualTo(16)
     }
 }
diff --git a/wear/watchface/watchface-complications-data-source-samples/lint-baseline.xml b/wear/watchface/watchface-complications-data-source-samples/lint-baseline.xml
new file mode 100644
index 0000000..cca6474
--- /dev/null
+++ b/wear/watchface/watchface-complications-data-source-samples/lint-baseline.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
+
+    <issue
+        id="NewApi"
+        message="`&lt;androidx.wear.watchface.complications.datasource.samples.ColorRampDataSourceService>` requires API level 33 (current min is 29)"
+        errorLine1="        &lt;service"
+        errorLine2="        ^">
+        <location
+            file="src/main/AndroidManifest.xml"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="`&lt;androidx.wear.watchface.complications.datasource.samples.NonInterpolatedColorRampDataSourceService>` requires API level 33 (current min is 29)"
+        errorLine1="        &lt;service"
+        errorLine2="        ^">
+        <location
+            file="src/main/AndroidManifest.xml"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="`&lt;androidx.wear.watchface.complications.datasource.samples.GoalProgressDataSourceService>` requires API level 33 (current min is 29)"
+        errorLine1="        &lt;service"
+        errorLine2="        ^">
+        <location
+            file="src/main/AndroidManifest.xml"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="`&lt;androidx.wear.watchface.complications.datasource.samples.WeightedElementDataSourceService>` requires API level 33 (current min is 29)"
+        errorLine1="        &lt;service"
+        errorLine2="        ^">
+        <location
+            file="src/main/AndroidManifest.xml"/>
+    </issue>
+
+</issues>
diff --git a/wear/watchface/watchface/lint-baseline.xml b/wear/watchface/watchface/lint-baseline.xml
index c29c2c7..7a622ac 100644
--- a/wear/watchface/watchface/lint-baseline.xml
+++ b/wear/watchface/watchface/lint-baseline.xml
@@ -1,5 +1,14 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.3.0-alpha10" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha10)" variant="all" version="8.3.0-alpha10">
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
+
+    <issue
+        id="NewApi"
+        message="`&lt;androidx.wear.watchface.control.WatchFaceControlService>` requires API level 27 (current min is 26)"
+        errorLine1="    &lt;service"
+        errorLine2="    ^">
+        <location
+            file="src/main/AndroidManifest.xml"/>
+    </issue>
 
     <issue
         id="VisibleForTests"
diff --git a/work/work-lint/src/main/java/androidx/work/lint/WorkManagerIssueRegistry.kt b/work/work-lint/src/main/java/androidx/work/lint/WorkManagerIssueRegistry.kt
index 8c08e6d..e1a29d8 100644
--- a/work/work-lint/src/main/java/androidx/work/lint/WorkManagerIssueRegistry.kt
+++ b/work/work-lint/src/main/java/androidx/work/lint/WorkManagerIssueRegistry.kt
@@ -25,7 +25,7 @@
 
 class WorkManagerIssueRegistry : IssueRegistry() {
     override val minApi = CURRENT_API
-    override val api = 14
+    override val api = 16
     override val issues: List<Issue> =
         listOf(
             BadConfigurationProviderIssueDetector.ISSUE,
diff --git a/work/work-runtime/lint-baseline.xml b/work/work-runtime/lint-baseline.xml
index ef9b99b..c3701b2 100644
--- a/work/work-runtime/lint-baseline.xml
+++ b/work/work-runtime/lint-baseline.xml
@@ -1,32 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.7.0-alpha02" type="baseline" client="gradle" dependencies="false" name="AGP (8.7.0-alpha02)" variant="all" version="8.7.0-alpha02">
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="        val id = idsToProcess.removeLastKt()"
-        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/work/impl/utils/CancelWorkRunnable.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="        val current = continuations.removeLastKt()"
-        errorLine2="                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/work/impl/utils/EnqueueUtils.kt"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="This Kotlin extension function will be hidden by `java.util.SequencedCollection` starting in API 35"
-        errorLine1="            val id = idsToProcess.removeLastKt()"
-        errorLine2="                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/work/impl/WorkerWrapper.kt"/>
-    </issue>
+<issues format="6" by="lint 8.8.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.8.0-alpha01)" variant="all" version="8.8.0-alpha01">
 
     <issue
         id="BanSynchronizedMethods"