Merge "Fix crash in cubic easing when 1ulp away from 1.0f" into androidx-main
diff --git a/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityResultTest.kt b/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityResultTest.kt
index 7678999..322f5ec 100644
--- a/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityResultTest.kt
+++ b/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityResultTest.kt
@@ -77,8 +77,8 @@
             scenario.withActivity { }
 
             scenario.withActivity {
-                assertThat(firstLaunchCount).isEqualTo(0)
-                assertThat(secondLaunchCount).isEqualTo(1)
+                assertThat(launchCountDownLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+                assertThat(launchedList).containsExactly("second")
             }
         }
     }
@@ -179,19 +179,21 @@
 
 class RegisterBeforeOnCreateActivity : ComponentActivity() {
     lateinit var launcher: ActivityResultLauncher<Intent>
-    var firstLaunchCount = 0
-    var secondLaunchCount = 0
+    var launchCountDownLatch = CountDownLatch(1)
+    val launchedList = mutableListOf<String>()
     var recreated = false
 
     init {
         addOnContextAvailableListener {
             launcher = if (!recreated) {
                 registerForActivityResult(StartActivityForResult()) {
-                    firstLaunchCount++
+                    launchedList.add("first")
+                    launchCountDownLatch.countDown()
                 }
             } else {
                 registerForActivityResult(StartActivityForResult()) {
-                    secondLaunchCount++
+                    launchedList.add("second")
+                    launchCountDownLatch.countDown()
                 }
             }
         }
diff --git a/activity/activity/src/main/java/androidx/activity/result/contract/ActivityResultContracts.kt b/activity/activity/src/main/java/androidx/activity/result/contract/ActivityResultContracts.kt
index bcfcf26..a391c75 100644
--- a/activity/activity/src/main/java/androidx/activity/result/contract/ActivityResultContracts.kt
+++ b/activity/activity/src/main/java/androidx/activity/result/contract/ActivityResultContracts.kt
@@ -33,6 +33,7 @@
 import androidx.activity.result.PickVisualMediaRequest
 import androidx.activity.result.contract.ActivityResultContracts.GetMultipleContents.Companion.getClipDataUris
 import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.Companion.ACTION_SYSTEM_FALLBACK_PICK_IMAGES
+import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.Companion.EXTRA_SYSTEM_FALLBACK_PICK_IMAGES_MAX
 import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.Companion.GMS_ACTION_PICK_IMAGES
 import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.Companion.GMS_EXTRA_PICK_IMAGES_MAX
 import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia.Companion.getGmsPicker
@@ -899,7 +900,7 @@
                 Intent(ACTION_SYSTEM_FALLBACK_PICK_IMAGES).apply {
                     setClassName(fallbackPicker.applicationInfo.packageName, fallbackPicker.name)
                     type = PickVisualMedia.getVisualMimeType(input.mediaType)
-                    putExtra(GMS_EXTRA_PICK_IMAGES_MAX, maxItems)
+                    putExtra(EXTRA_SYSTEM_FALLBACK_PICK_IMAGES_MAX, maxItems)
                 }
             } else if (PickVisualMedia.isGmsPickerAvailable(context)) {
                 val gmsPicker = checkNotNull(getGmsPicker(context)).activityInfo
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 d20b0f7..3833958 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
@@ -114,7 +114,7 @@
                 mAppSearchDir,
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
@@ -501,7 +501,7 @@
         mAppSearchImpl.close();
         mAppSearchImpl = AppSearchImpl.create(
                 mAppSearchDir, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()),
+                        new LocalStorageIcingOptionsConfig()),
                 initStatsBuilder, ALWAYS_OPTIMIZE, /*visibilityChecker=*/null);
 
         // Check recovery state
@@ -733,7 +733,7 @@
                 tempFolder,
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
@@ -909,7 +909,7 @@
                 tempFolder,
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
@@ -2867,7 +2867,7 @@
                 mAppSearchDir,
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
@@ -2939,7 +2939,7 @@
                 mAppSearchDir,
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
@@ -3018,7 +3018,7 @@
                 mAppSearchDir,
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
@@ -3145,7 +3145,7 @@
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
-                }, new DefaultIcingOptionsConfig()),
+                }, new LocalStorageIcingOptionsConfig()),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
@@ -3224,7 +3224,7 @@
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
-                }, new DefaultIcingOptionsConfig()),
+                }, new LocalStorageIcingOptionsConfig()),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
@@ -3281,7 +3281,7 @@
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
-                }, new DefaultIcingOptionsConfig()),
+                }, new LocalStorageIcingOptionsConfig()),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
@@ -3318,7 +3318,7 @@
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
-                }, new DefaultIcingOptionsConfig()),
+                }, new LocalStorageIcingOptionsConfig()),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
@@ -3431,7 +3431,7 @@
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
-                }, new DefaultIcingOptionsConfig()),
+                }, new LocalStorageIcingOptionsConfig()),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
@@ -3527,7 +3527,7 @@
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
-                }, new DefaultIcingOptionsConfig()),
+                }, new LocalStorageIcingOptionsConfig()),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
@@ -3584,7 +3584,7 @@
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
-                }, new DefaultIcingOptionsConfig()),
+                }, new LocalStorageIcingOptionsConfig()),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
@@ -3737,7 +3737,7 @@
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
-                }, new DefaultIcingOptionsConfig()),
+                }, new LocalStorageIcingOptionsConfig()),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
@@ -3820,7 +3820,7 @@
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
-                }, new DefaultIcingOptionsConfig()),
+                }, new LocalStorageIcingOptionsConfig()),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
@@ -3877,7 +3877,7 @@
                     public int getMaxSuggestionCount() {
                         return Integer.MAX_VALUE;
                     }
-                }, new DefaultIcingOptionsConfig()),
+                }, new LocalStorageIcingOptionsConfig()),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
@@ -3923,7 +3923,7 @@
                     public int getMaxSuggestionCount() {
                         return 2;
                     }
-                }, new DefaultIcingOptionsConfig()),
+                }, new LocalStorageIcingOptionsConfig()),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
@@ -4026,7 +4026,7 @@
                 tempFolder,
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
@@ -4079,7 +4079,7 @@
                 tempFolder,
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
@@ -4130,7 +4130,7 @@
                 tempFolder,
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
@@ -4183,7 +4183,7 @@
                 tempFolder,
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
@@ -4522,7 +4522,7 @@
                 mAppSearchDir,
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
@@ -4563,7 +4563,7 @@
                 mAppSearchDir,
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
@@ -4596,7 +4596,7 @@
                 tempFolder,
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
@@ -4694,7 +4694,7 @@
                 tempFolder,
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
@@ -4783,7 +4783,7 @@
                 mAppSearchDir,
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 /*initStatsBuilder=*/null,
                 ALWAYS_OPTIMIZE,
@@ -4887,7 +4887,7 @@
                 mAppSearchDir,
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 /*initStatsBuilder=*/null,
                 ALWAYS_OPTIMIZE,
@@ -4949,7 +4949,7 @@
                 mAppSearchDir,
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 /*initStatsBuilder=*/null,
                 ALWAYS_OPTIMIZE,
@@ -5277,7 +5277,7 @@
                 mAppSearchDir,
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 /*initStatsBuilder=*/null,
                 ALWAYS_OPTIMIZE,
@@ -5435,7 +5435,7 @@
                 mAppSearchDir,
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 /*initStatsBuilder=*/null,
                 ALWAYS_OPTIMIZE,
@@ -5524,7 +5524,7 @@
                 mAppSearchDir,
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 /*initStatsBuilder=*/null,
                 ALWAYS_OPTIMIZE,
@@ -5617,7 +5617,7 @@
                 mAppSearchDir,
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 /*initStatsBuilder=*/null,
                 ALWAYS_OPTIMIZE,
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchLoggerTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchLoggerTest.java
index 22afe67..e9b8e47 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchLoggerTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/AppSearchLoggerTest.java
@@ -77,7 +77,7 @@
                 mTemporaryFolder.newFolder(),
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
@@ -370,7 +370,7 @@
                 mTemporaryFolder.newFolder(),
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 initStatsBuilder,
                 ALWAYS_OPTIMIZE,
@@ -403,7 +403,7 @@
                 folder,
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
@@ -442,7 +442,7 @@
         InitializeStats.Builder initStatsBuilder = new InitializeStats.Builder();
         appSearchImpl = AppSearchImpl.create(
                 folder, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()),
+                        new LocalStorageIcingOptionsConfig()),
                 initStatsBuilder, ALWAYS_OPTIMIZE, /*visibilityChecker=*/null);
         InitializeStats iStats = initStatsBuilder.build();
 
@@ -470,7 +470,7 @@
 
         AppSearchImpl appSearchImpl = AppSearchImpl.create(
                 folder, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()),
+                        new LocalStorageIcingOptionsConfig()),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE, /*visibilityChecker=*/null);
 
         List<AppSearchSchema> schemas = ImmutableList.of(
@@ -510,7 +510,7 @@
         InitializeStats.Builder initStatsBuilder = new InitializeStats.Builder();
         appSearchImpl = AppSearchImpl.create(
                 folder, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()),
+                        new LocalStorageIcingOptionsConfig()),
                 initStatsBuilder, ALWAYS_OPTIMIZE, /*visibilityChecker=*/null);
         InitializeStats iStats = initStatsBuilder.build();
 
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/SearchResultsImplTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/SearchResultsImplTest.java
index dfbc317..0690cd5 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/SearchResultsImplTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/SearchResultsImplTest.java
@@ -53,7 +53,7 @@
                 mTemporaryFolder.newFolder(),
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 /*initStatsBuilder=*/ null, ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/GenericDocumentToProtoConverterTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/GenericDocumentToProtoConverterTest.java
index 59b2e66..a8bb683 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/GenericDocumentToProtoConverterTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/GenericDocumentToProtoConverterTest.java
@@ -20,7 +20,7 @@
 
 import androidx.appsearch.app.GenericDocument;
 import androidx.appsearch.localstorage.AppSearchConfigImpl;
-import androidx.appsearch.localstorage.DefaultIcingOptionsConfig;
+import androidx.appsearch.localstorage.LocalStorageIcingOptionsConfig;
 import androidx.appsearch.localstorage.UnlimitedLimitConfig;
 
 import com.google.android.icing.proto.DocumentProto;
@@ -119,7 +119,7 @@
         GenericDocument convertedGenericDocument =
                 GenericDocumentToProtoConverter.toGenericDocument(documentProto, PREFIX,
                         SCHEMA_MAP, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
-                                new DefaultIcingOptionsConfig()));
+                                new LocalStorageIcingOptionsConfig()));
         DocumentProto convertedDocumentProto =
                 GenericDocumentToProtoConverter.toDocumentProto(document);
 
@@ -217,7 +217,7 @@
         GenericDocument convertedGenericDocument =
                 GenericDocumentToProtoConverter.toGenericDocument(documentProto, PREFIX,
                         schemaMap, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
-                                new DefaultIcingOptionsConfig()));
+                                new LocalStorageIcingOptionsConfig()));
         DocumentProto convertedDocumentProto =
                 GenericDocumentToProtoConverter.toDocumentProto(document);
         assertThat(convertedDocumentProto).isEqualTo(documentProto);
@@ -346,7 +346,7 @@
         GenericDocument convertedGenericDocument =
                 GenericDocumentToProtoConverter.toGenericDocument(outerDocumentProto, PREFIX,
                         schemaMap, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
-                                new DefaultIcingOptionsConfig()));
+                                new LocalStorageIcingOptionsConfig()));
         DocumentProto convertedDocumentProto =
                 GenericDocumentToProtoConverter.toDocumentProto(outerDocument);
         assertThat(convertedDocumentProto).isEqualTo(outerDocumentProto);
@@ -409,13 +409,13 @@
         GenericDocument actualDocWithParentAsMetaField =
                 GenericDocumentToProtoConverter.toGenericDocument(documentProto, PREFIX,
                         schemaMap, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
-                                new DefaultIcingOptionsConfig(),
+                                new LocalStorageIcingOptionsConfig(),
                                 /* storeParentInfoAsSyntheticProperty= */ false,
                                 /* shouldRetrieveParentInfo= */ true));
         GenericDocument actualDocWithParentAsSyntheticProperty =
                 GenericDocumentToProtoConverter.toGenericDocument(documentProto, PREFIX,
                         schemaMap, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
-                                new DefaultIcingOptionsConfig(),
+                                new LocalStorageIcingOptionsConfig(),
                                 /* storeParentInfoAsSyntheticProperty= */ true,
                                 /* shouldRetrieveParentInfo= */ true));
 
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchResultToProtoConverterTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchResultToProtoConverterTest.java
index 11e0e8e..ccf3b51 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchResultToProtoConverterTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchResultToProtoConverterTest.java
@@ -26,7 +26,7 @@
 import androidx.appsearch.app.SearchResultPage;
 import androidx.appsearch.exceptions.AppSearchException;
 import androidx.appsearch.localstorage.AppSearchConfigImpl;
-import androidx.appsearch.localstorage.DefaultIcingOptionsConfig;
+import androidx.appsearch.localstorage.LocalStorageIcingOptionsConfig;
 import androidx.appsearch.localstorage.UnlimitedLimitConfig;
 import androidx.appsearch.localstorage.util.PrefixUtil;
 
@@ -49,7 +49,7 @@
         final String namespace = prefix + "namespace";
         final String schemaType = prefix + "schema";
         final AppSearchConfigImpl config = new AppSearchConfigImpl(new UnlimitedLimitConfig(),
-                new DefaultIcingOptionsConfig());
+                new LocalStorageIcingOptionsConfig());
 
         // Building the SearchResult received from query.
         DocumentProto.Builder documentProtoBuilder = DocumentProto.newBuilder()
@@ -150,7 +150,7 @@
         Exception e = assertThrows(AppSearchException.class,
                 () -> SearchResultToProtoConverter.toSearchResultPage(searchResultProto, schemaMap,
                         new AppSearchConfigImpl(new UnlimitedLimitConfig(),
-                                new DefaultIcingOptionsConfig())));
+                                new LocalStorageIcingOptionsConfig())));
         assertThat(e.getMessage())
                 .isEqualTo("Nesting joined results within joined results not allowed.");
     }
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverterTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverterTest.java
index 2f3b067..92a86eb 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverterTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SearchSpecToProtoConverterTest.java
@@ -27,8 +27,8 @@
 import androidx.appsearch.app.SearchSpec;
 import androidx.appsearch.localstorage.AppSearchConfigImpl;
 import androidx.appsearch.localstorage.AppSearchImpl;
-import androidx.appsearch.localstorage.DefaultIcingOptionsConfig;
 import androidx.appsearch.localstorage.IcingOptionsConfig;
+import androidx.appsearch.localstorage.LocalStorageIcingOptionsConfig;
 import androidx.appsearch.localstorage.OptimizeStrategy;
 import androidx.appsearch.localstorage.UnlimitedLimitConfig;
 import androidx.appsearch.localstorage.util.PrefixUtil;
@@ -64,7 +64,8 @@
     @Rule
     public final TemporaryFolder mTemporaryFolder = new TemporaryFolder();
 
-    private final IcingOptionsConfig mDefaultIcingOptionsConfig = new DefaultIcingOptionsConfig();
+    private final IcingOptionsConfig mLocalStorageIcingOptionsConfig =
+            new LocalStorageIcingOptionsConfig();
 
     private AppSearchImpl mAppSearchImpl;
 
@@ -74,7 +75,7 @@
                 mTemporaryFolder.newFolder(),
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        mDefaultIcingOptionsConfig
+                        mLocalStorageIcingOptionsConfig
                 ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
@@ -111,7 +112,7 @@
                 prefix2, ImmutableMap.of(
                         prefix2 + "typeA", configProto,
                         prefix2 + "typeB", configProto)),
-                mDefaultIcingOptionsConfig);
+                mLocalStorageIcingOptionsConfig);
         // Convert SearchSpec to proto.
         SearchSpecProto searchSpecProto = converter.toSearchSpecProto();
 
@@ -161,7 +162,7 @@
                 prefix2, ImmutableMap.of(
                         prefix2 + "typeA", configProto,
                         prefix2 + "typeB", configProto)),
-                mDefaultIcingOptionsConfig);
+                mLocalStorageIcingOptionsConfig);
 
         // Convert SearchSpec to proto.
         SearchSpecProto searchSpecProto = converter.toSearchSpecProto();
@@ -235,7 +236,7 @@
                         ImmutableMap.of(
                                 prefix2 + "typeA", configProto,
                                 prefix2 + "typeB", configProto)),
-                        mDefaultIcingOptionsConfig);
+                        mLocalStorageIcingOptionsConfig);
 
         VisibilityStore visibilityStore = new VisibilityStore(mAppSearchImpl);
         converter.removeInaccessibleSchemaFilter(
@@ -288,7 +289,7 @@
                 /*namespaceMap=*/ImmutableMap.of(prefix, ImmutableSet.of(prefix + namespace)),
                 /*schemaMap=*/ImmutableMap.of(prefix, ImmutableMap.of(prefix + schemaType,
                 SchemaTypeConfigProto.getDefaultInstance())),
-                mDefaultIcingOptionsConfig).toScoringSpecProto();
+                mLocalStorageIcingOptionsConfig).toScoringSpecProto();
         TypePropertyWeights typePropertyWeights = TypePropertyWeights.newBuilder()
                 .setSchemaType(prefix + schemaType)
                 .addPropertyWeights(PropertyWeight.newBuilder()
@@ -317,7 +318,7 @@
                 /*prefixes=*/ImmutableSet.of(),
                 /*namespaceMap=*/ImmutableMap.of(),
                 /*schemaMap=*/ImmutableMap.of(),
-                mDefaultIcingOptionsConfig).toScoringSpecProto();
+                mLocalStorageIcingOptionsConfig).toScoringSpecProto();
 
         assertThat(scoringSpecProto.getOrderBy().getNumber())
                 .isEqualTo(ScoringSpecProto.Order.Code.ASC_VALUE);
@@ -342,7 +343,7 @@
                 /*prefixes=*/ImmutableSet.of(),
                 /*namespaceMap=*/ImmutableMap.of(),
                 /*schemaMap=*/ImmutableMap.of(),
-                mDefaultIcingOptionsConfig);
+                mLocalStorageIcingOptionsConfig);
         ResultSpecProto resultSpecProto = convert.toResultSpecProto(
                 /*namespaceMap=*/ImmutableMap.of(),
                 /*schemaMap=*/ImmutableMap.of());
@@ -380,7 +381,7 @@
                 /*prefixes=*/ImmutableSet.of(),
                 /*namespaceMap=*/ImmutableMap.of(),
                 /*schemaMap=*/ImmutableMap.of(),
-                mDefaultIcingOptionsConfig);
+                mLocalStorageIcingOptionsConfig);
 
         ResultSpecProto resultSpecProto = converter.toResultSpecProto(
                 /*namespaceMap=*/ImmutableMap.of(),
@@ -428,7 +429,7 @@
                 /*prefixes=*/ImmutableSet.of(personPrefix, actionPrefix),
                 namespaceMap,
                 schemaMap,
-                mDefaultIcingOptionsConfig);
+                mLocalStorageIcingOptionsConfig);
 
         ResultSpecProto resultSpecProto = converter.toResultSpecProto(
                 namespaceMap,
@@ -479,7 +480,7 @@
                 /*prefixes=*/ImmutableSet.of(personPrefix, actionPrefix),
                 namespaceMap,
                 schemaMap,
-                mDefaultIcingOptionsConfig);
+                mLocalStorageIcingOptionsConfig);
 
         ResultSpecProto resultSpecProto = converter.toResultSpecProto(
                 namespaceMap,
@@ -535,7 +536,7 @@
                 /*prefixes=*/ImmutableSet.of(personPrefix, actionPrefix),
                 namespaceMap,
                 schemaMap,
-                mDefaultIcingOptionsConfig);
+                mLocalStorageIcingOptionsConfig);
 
         SearchSpecProto searchSpecProto = converter.toSearchSpecProto();
 
@@ -589,7 +590,7 @@
                 /*prefixes=*/ImmutableSet.of(personPrefix, actionPrefix),
                 namespaceMap,
                 schemaMap,
-                mDefaultIcingOptionsConfig);
+                mLocalStorageIcingOptionsConfig);
 
         ScoringSpecProto scoringSpecProto = converter.toScoringSpecProto();
 
@@ -627,7 +628,7 @@
                 /*prefixes=*/ImmutableSet.of(prefix1, prefix2),
                 /*namespaceMap=*/ImmutableMap.of(),
                 /*schemaMap=*/ImmutableMap.of(),
-                mDefaultIcingOptionsConfig);
+                mLocalStorageIcingOptionsConfig);
         ResultSpecProto resultSpecProto = converter.toResultSpecProto(
                 /*namespaceMap=*/ImmutableMap.of(
                         prefix1, ImmutableSet.of(
@@ -680,7 +681,7 @@
                 /*prefixes=*/ImmutableSet.of(prefix1, prefix2),
                 namespaceMap,
                 /*schemaMap=*/ImmutableMap.of(),
-                mDefaultIcingOptionsConfig);
+                mLocalStorageIcingOptionsConfig);
         ResultSpecProto resultSpecProto = converter.toResultSpecProto(
                 namespaceMap,
                 /*schemaMap=*/ImmutableMap.of());
@@ -729,7 +730,7 @@
                 /*prefixes=*/ImmutableSet.of(prefix1, prefix2),
                 /*namespaceMap=*/ImmutableMap.of(),
                 schemaMap,
-                mDefaultIcingOptionsConfig);
+                mLocalStorageIcingOptionsConfig);
         ResultSpecProto resultSpecProto = converter.toResultSpecProto(
                 /*namespaceMap=*/ImmutableMap.of(),
                 schemaMap);
@@ -775,7 +776,7 @@
                 searchSpec,
                 /*prefixes=*/ImmutableSet.of(prefix1, prefix2),
                 namespaceMap, /*schemaMap=*/ImmutableMap.of(),
-                mDefaultIcingOptionsConfig);
+                mLocalStorageIcingOptionsConfig);
         ResultSpecProto resultSpecProto = converter.toResultSpecProto(
                 namespaceMap,
                 /*schemaMap=*/ImmutableMap.of());
@@ -814,7 +815,7 @@
                 /*prefixes=*/ImmutableSet.of(prefix1, prefix2),
                 /*namespaceMap=*/ImmutableMap.of(),
                 schemaMap,
-                mDefaultIcingOptionsConfig);
+                mLocalStorageIcingOptionsConfig);
         ResultSpecProto resultSpecProto = converter.toResultSpecProto(
                 /*namespaceMap=*/ImmutableMap.of(),
                 schemaMap);
@@ -858,7 +859,7 @@
                 /*prefixes=*/ImmutableSet.of(prefix1, prefix2),
                 namespaceMap,
                 schemaMap,
-                mDefaultIcingOptionsConfig);
+                mLocalStorageIcingOptionsConfig);
         ResultSpecProto resultSpecProto = converter.toResultSpecProto(namespaceMap, schemaMap);
 
         assertThat(resultSpecProto.getResultGroupingsCount()).isEqualTo(4);
@@ -950,7 +951,7 @@
                 /*prefixes=*/ImmutableSet.of(prefix1, prefix2),
                 namespaceMap,
                 schemaMap,
-                mDefaultIcingOptionsConfig);
+                mLocalStorageIcingOptionsConfig);
         ResultSpecProto resultSpecProto = converter.toResultSpecProto(namespaceMap, schemaMap);
 
         assertThat(resultSpecProto.getResultGroupingsCount()).isEqualTo(8);
@@ -1045,7 +1046,7 @@
                 searchSpec,
                 /*prefixes=*/ImmutableSet.of(prefix1, prefix2),
                 namespaceMap, /*schemaMap=*/ImmutableMap.of(),
-                mDefaultIcingOptionsConfig);
+                mLocalStorageIcingOptionsConfig);
 
         SearchSpecProto searchSpecProto = converter.toSearchSpecProto();
 
@@ -1071,7 +1072,7 @@
                 prefix2, ImmutableSet.of("package$database2/namespace3",
                         "package$database2/namespace4")),
                 /*schemaMap=*/ImmutableMap.of(),
-                mDefaultIcingOptionsConfig);
+                mLocalStorageIcingOptionsConfig);
 
         SearchSpecProto searchSpecProto = converter.toSearchSpecProto();
         // Only search prefix1 will return namespace 1 and 2.
@@ -1094,7 +1095,7 @@
                 prefix1, ImmutableSet.of("package$database1/namespace1",
                         "package$database1/namespace2")),
                 /*schemaMap=*/ImmutableMap.of(),
-                mDefaultIcingOptionsConfig);
+                mLocalStorageIcingOptionsConfig);
         SearchSpecProto searchSpecProto = converter.toSearchSpecProto();
         // If the searching namespace filter is not empty, the target namespace filter will be the
         // intersection of the searching namespace filters that users want to search over and
@@ -1118,7 +1119,7 @@
                 prefix1, ImmutableSet.of("package$database1/namespace1",
                         "package$database1/namespace2")),
                 /*schemaMap=*/ImmutableMap.of(),
-                mDefaultIcingOptionsConfig);
+                mLocalStorageIcingOptionsConfig);
         SearchSpecProto searchSpecProto = converter.toSearchSpecProto();
         // If the searching namespace filter is not empty, the target namespace filter will be the
         // intersection of the searching namespace filters that users want to search over and
@@ -1146,7 +1147,7 @@
                 prefix2, ImmutableMap.of(
                         "package$database2/typeC", schemaTypeConfigProto,
                         "package$database2/typeD", schemaTypeConfigProto)),
-                mDefaultIcingOptionsConfig);
+                mLocalStorageIcingOptionsConfig);
         SearchSpecProto searchSpecProto = converter.toSearchSpecProto();
         // Empty searching filter will get all types for target filter
         assertThat(searchSpecProto.getSchemaTypeFiltersList()).containsExactly(
@@ -1175,7 +1176,7 @@
                 prefix2, ImmutableMap.of(
                         "package$database2/typeC", schemaTypeConfigProto,
                         "package$database2/typeD", schemaTypeConfigProto)),
-                mDefaultIcingOptionsConfig);
+                mLocalStorageIcingOptionsConfig);
         SearchSpecProto searchSpecProto = converter.toSearchSpecProto();
         // Only search prefix1 will return typeA and B.
         assertThat(searchSpecProto.getSchemaTypeFiltersList()).containsExactly(
@@ -1200,7 +1201,7 @@
                 prefix1, ImmutableMap.of(
                         "package$database1/typeA", schemaTypeConfigProto,
                         "package$database1/typeB", schemaTypeConfigProto)),
-                mDefaultIcingOptionsConfig);
+                mLocalStorageIcingOptionsConfig);
         SearchSpecProto searchSpecProto = converter.toSearchSpecProto();
         // If the searching schema filter is not empty, the target schema filter will be the
         // intersection of the schema filters that users want to search over and those candidates
@@ -1227,7 +1228,7 @@
                 prefix1, ImmutableMap.of(
                         "package$database1/typeA", schemaTypeConfigProto,
                         "package$database1/typeB", schemaTypeConfigProto)),
-                mDefaultIcingOptionsConfig);
+                mLocalStorageIcingOptionsConfig);
         SearchSpecProto searchSpecProto = converter.toSearchSpecProto();
         // If there is no intersection of the schema filters that user want to search over and
         // those filters which are stored in AppSearch, return empty.
@@ -1257,7 +1258,7 @@
                         "package$database/schema1", schemaTypeConfigProto,
                         "package$database/schema2", schemaTypeConfigProto,
                         "package$database/schema3", schemaTypeConfigProto)),
-                mDefaultIcingOptionsConfig);
+                mLocalStorageIcingOptionsConfig);
 
         converter.removeInaccessibleSchemaFilter(
                 new CallerAccess(/*callingPackageName=*/"otherPackageName"),
@@ -1304,7 +1305,7 @@
                         searchSpec, /*prefixes=*/ImmutableSet.of(prefix),
                         /*namespaceMap=*/namespaceMap,
                         /*schemaMap=*/ImmutableMap.of(),
-                        mDefaultIcingOptionsConfig);
+                        mLocalStorageIcingOptionsConfig);
         assertThat(emptySchemaConverter.hasNothingToSearch()).isTrue();
 
         SearchSpecToProtoConverter emptyNamespaceConverter =
@@ -1313,7 +1314,7 @@
                         searchSpec, /*prefixes=*/ImmutableSet.of(prefix),
                         /*namespaceMap=*/ImmutableMap.of(),
                         schemaMap,
-                        mDefaultIcingOptionsConfig);
+                        mLocalStorageIcingOptionsConfig);
         assertThat(emptyNamespaceConverter.hasNothingToSearch()).isTrue();
 
         SearchSpecToProtoConverter nonEmptyConverter =
@@ -1321,7 +1322,7 @@
                         /*queryExpression=*/"",
                         searchSpec, /*prefixes=*/ImmutableSet.of(prefix),
                         namespaceMap, schemaMap,
-                        mDefaultIcingOptionsConfig);
+                        mLocalStorageIcingOptionsConfig);
         assertThat(nonEmptyConverter.hasNothingToSearch()).isFalse();
 
         // remove all target schema filter, and the query becomes nothing to search.
@@ -1359,7 +1360,7 @@
                         "package$database/schema1", schemaTypeConfigProto,
                         "package$database/schema2", schemaTypeConfigProto,
                         "package$database/schema3", schemaTypeConfigProto)),
-                mDefaultIcingOptionsConfig);
+                mLocalStorageIcingOptionsConfig);
 
         converter.removeInaccessibleSchemaFilter(
                 new CallerAccess(/*callingPackageName=*/"otherPackageName"),
@@ -1407,7 +1408,7 @@
                         searchSpec, /*prefixes=*/ImmutableSet.of(prefix1, prefix2),
                         namespaceMap,
                         schemaTypeMap,
-                        mDefaultIcingOptionsConfig);
+                        mLocalStorageIcingOptionsConfig);
 
         TypePropertyWeights expectedTypePropertyWeight1 =
                 TypePropertyWeights.newBuilder().setSchemaType(prefix1 + schemaTypeA)
@@ -1458,7 +1459,7 @@
                         /*schemaMap=*/ImmutableMap.of(
                         prefix1,
                         ImmutableMap.of(prefix1 + "typeA", schemaTypeConfigProto)),
-                        mDefaultIcingOptionsConfig);
+                        mLocalStorageIcingOptionsConfig);
 
         ScoringSpecProto convertedScoringSpecProto = converter.toScoringSpecProto();
 
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SnippetTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SnippetTest.java
index 026424f..fbc9a93 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SnippetTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/converter/SnippetTest.java
@@ -22,7 +22,7 @@
 import androidx.appsearch.app.SearchResult;
 import androidx.appsearch.app.SearchResultPage;
 import androidx.appsearch.localstorage.AppSearchConfigImpl;
-import androidx.appsearch.localstorage.DefaultIcingOptionsConfig;
+import androidx.appsearch.localstorage.LocalStorageIcingOptionsConfig;
 import androidx.appsearch.localstorage.UnlimitedLimitConfig;
 import androidx.appsearch.localstorage.util.PrefixUtil;
 
@@ -96,7 +96,7 @@
         SearchResultPage searchResultPage = SearchResultToProtoConverter.toSearchResultPage(
                 searchResultProto,
                 SCHEMA_MAP, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()));
+                        new LocalStorageIcingOptionsConfig()));
         assertThat(searchResultPage.getResults()).hasSize(1);
         SearchResult.MatchInfo match = searchResultPage.getResults().get(0).getMatchInfos().get(0);
         assertThat(match.getPropertyPath()).isEqualTo(propertyKeyString);
@@ -137,7 +137,7 @@
         SearchResultPage searchResultPage = SearchResultToProtoConverter.toSearchResultPage(
                 searchResultProto,
                 SCHEMA_MAP, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()));
+                        new LocalStorageIcingOptionsConfig()));
         assertThat(searchResultPage.getResults()).hasSize(1);
         assertThat(searchResultPage.getResults().get(0).getMatchInfos()).isEmpty();
     }
@@ -194,7 +194,7 @@
         SearchResultPage searchResultPage = SearchResultToProtoConverter.toSearchResultPage(
                 searchResultProto,
                 SCHEMA_MAP, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()));
+                        new LocalStorageIcingOptionsConfig()));
         assertThat(searchResultPage.getResults()).hasSize(1);
         SearchResult.MatchInfo match1 = searchResultPage.getResults().get(0).getMatchInfos().get(0);
         assertThat(match1.getPropertyPath()).isEqualTo("senderName");
@@ -281,7 +281,7 @@
         SearchResultPage searchResultPage = SearchResultToProtoConverter.toSearchResultPage(
                 searchResultProto,
                 SCHEMA_MAP, new AppSearchConfigImpl(new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()));
+                        new LocalStorageIcingOptionsConfig()));
         assertThat(searchResultPage.getResults()).hasSize(1);
         SearchResult.MatchInfo match1 = searchResultPage.getResults().get(0).getMatchInfos().get(0);
         assertThat(match1.getPropertyPath()).isEqualTo("sender.name");
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStoreMigrationHelperFromV0Test.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStoreMigrationHelperFromV0Test.java
index 77772c4..7b03a10 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStoreMigrationHelperFromV0Test.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStoreMigrationHelperFromV0Test.java
@@ -33,7 +33,7 @@
 import androidx.appsearch.app.VisibilityDocument;
 import androidx.appsearch.localstorage.AppSearchConfigImpl;
 import androidx.appsearch.localstorage.AppSearchImpl;
-import androidx.appsearch.localstorage.DefaultIcingOptionsConfig;
+import androidx.appsearch.localstorage.LocalStorageIcingOptionsConfig;
 import androidx.appsearch.localstorage.OptimizeStrategy;
 import androidx.appsearch.localstorage.UnlimitedLimitConfig;
 import androidx.appsearch.localstorage.util.PrefixUtil;
@@ -130,7 +130,7 @@
         appSearchImplInV0.close();
         AppSearchImpl appSearchImpl = AppSearchImpl.create(mFile,
                 new AppSearchConfigImpl(new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()), /*initStatsBuilder=*/ null,
+                        new LocalStorageIcingOptionsConfig()), /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
@@ -196,7 +196,7 @@
         // Set deprecated visibility schema version 0 into AppSearchImpl.
         AppSearchImpl appSearchImpl = AppSearchImpl.create(mFile,
                 new AppSearchConfigImpl(new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()), /*initStatsBuilder=*/ null,
+                        new LocalStorageIcingOptionsConfig()), /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
         InternalSetSchemaResponse internalSetSchemaResponse = appSearchImpl.setSchema(
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStoreMigrationHelperFromV1Test.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStoreMigrationHelperFromV1Test.java
index c5bd8ac..a70da64 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStoreMigrationHelperFromV1Test.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStoreMigrationHelperFromV1Test.java
@@ -28,7 +28,7 @@
 import androidx.appsearch.app.VisibilityDocument;
 import androidx.appsearch.localstorage.AppSearchConfigImpl;
 import androidx.appsearch.localstorage.AppSearchImpl;
-import androidx.appsearch.localstorage.DefaultIcingOptionsConfig;
+import androidx.appsearch.localstorage.LocalStorageIcingOptionsConfig;
 import androidx.appsearch.localstorage.OptimizeStrategy;
 import androidx.appsearch.localstorage.UnlimitedLimitConfig;
 import androidx.appsearch.localstorage.util.PrefixUtil;
@@ -74,7 +74,7 @@
         // Create AppSearchImpl with visibility document version 1;
         AppSearchImpl appSearchImplInV1 = AppSearchImpl.create(mFile,
                 new AppSearchConfigImpl(new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()), /*initStatsBuilder=*/ null,
+                        new LocalStorageIcingOptionsConfig()), /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
         InternalSetSchemaResponse internalSetSchemaResponse = appSearchImplInV1.setSchema(
@@ -124,7 +124,7 @@
         appSearchImplInV1.close();
         AppSearchImpl appSearchImpl = AppSearchImpl.create(mFile,
                 new AppSearchConfigImpl(new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()), /*initStatsBuilder=*/ null,
+                        new LocalStorageIcingOptionsConfig()), /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
                 /*visibilityChecker=*/null);
 
diff --git a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStoreTest.java b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStoreTest.java
index 17e8a15..056acea 100644
--- a/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStoreTest.java
+++ b/appsearch/appsearch-local-storage/src/androidTest/java/androidx/appsearch/localstorage/visibilitystore/VisibilityStoreTest.java
@@ -27,7 +27,7 @@
 import androidx.appsearch.exceptions.AppSearchException;
 import androidx.appsearch.localstorage.AppSearchConfigImpl;
 import androidx.appsearch.localstorage.AppSearchImpl;
-import androidx.appsearch.localstorage.DefaultIcingOptionsConfig;
+import androidx.appsearch.localstorage.LocalStorageIcingOptionsConfig;
 import androidx.appsearch.localstorage.OptimizeStrategy;
 import androidx.appsearch.localstorage.UnlimitedLimitConfig;
 import androidx.appsearch.localstorage.util.PrefixUtil;
@@ -63,7 +63,7 @@
                 mAppSearchDir,
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig()
+                        new LocalStorageIcingOptionsConfig()
                 ),
                 /*initStatsBuilder=*/ null,
                 ALWAYS_OPTIMIZE,
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java
index 628af6a..db55481 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java
@@ -341,7 +341,7 @@
                 icingDir,
                 new AppSearchConfigImpl(
                         new UnlimitedLimitConfig(),
-                        new DefaultIcingOptionsConfig(),
+                        new LocalStorageIcingOptionsConfig(),
                         /* storeParentInfoAsSyntheticProperty= */ false,
                         /* shouldRetrieveParentInfo= */ true
                 ),
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/DefaultIcingOptionsConfig.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorageIcingOptionsConfig.java
similarity index 94%
rename from appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/DefaultIcingOptionsConfig.java
rename to appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorageIcingOptionsConfig.java
index 11a0c04..a328058 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/DefaultIcingOptionsConfig.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorageIcingOptionsConfig.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-// @exportToFramework:copyToPath(testing/testutils/src/android/app/appsearch/testutil/external/DefaultIcingOptionsConfig.java)
+// @exportToFramework:copyToPath(testing/testutils/src/android/app/appsearch/testutil/external/LocalStorageIcingOptionsConfig.java)
 package androidx.appsearch.localstorage;
 
 import androidx.annotation.RestrictTo;
@@ -23,7 +23,7 @@
  * set in {@link com.google.android.icing.proto.IcingSearchEngineOptions} proto.
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class DefaultIcingOptionsConfig implements IcingOptionsConfig {
+public class LocalStorageIcingOptionsConfig implements IcingOptionsConfig {
     @Override
     public int getMaxTokenLength() {
         return DEFAULT_MAX_TOKEN_LENGTH;
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/perfetto/SliceTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/perfetto/SliceTest.kt
new file mode 100644
index 0000000..f0f754b
--- /dev/null
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/perfetto/SliceTest.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.benchmark.perfetto
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import kotlin.test.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SliceTest {
+    @Test
+    fun frameId() {
+        assertEquals(
+            1234,
+            Slice("Choreographer#doFrame 1234", 1, 2).frameId
+        )
+    }
+
+    @Test
+    fun frameId_extended() {
+        // some OEMs have added additional metadata to standard tracepoints
+        // we'll fix these best effort as they are reported
+        assertEquals(
+            123,
+            Slice("Choreographer#doFrame 123 234 extra=91929", 1, 2).frameId
+        )
+    }
+}
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/FrameTimingQuery.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/FrameTimingQuery.kt
index 5064d8f..7cec6f9 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/FrameTimingQuery.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/FrameTimingQuery.kt
@@ -159,6 +159,7 @@
 
         val groupedData = slices
             .filter { it.dur > 0 } // drop non-terminated slices
+            .filter { !it.name.contains("resynced") } // drop "#doFrame - resynced to" slices
             .groupBy {
                 when {
                     // note: we use "startsWith" as starting in S, all of these will end
@@ -180,6 +181,11 @@
             return emptyList()
         }
 
+        check(rtSlices.isNotEmpty()) {
+            "Observed no renderthread slices in trace - verify that your benchmark is redrawing" +
+                " and is hardware accelerated (which is the default)."
+        }
+
         return if (captureApiLevel >= 31) {
             check(actualSlices.isNotEmpty() && expectedSlices.isNotEmpty()) {
                 "Observed no expect/actual slices in trace," +
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/perfetto/PerfettoTraceProcessor.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/perfetto/PerfettoTraceProcessor.kt
index c5bc405..63afa7d 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/perfetto/PerfettoTraceProcessor.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/perfetto/PerfettoTraceProcessor.kt
@@ -18,6 +18,9 @@
 
 import androidx.annotation.RestrictTo
 import androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP
+import androidx.benchmark.InstrumentationResults
+import androidx.benchmark.Outputs
+import androidx.benchmark.Profiler
 import androidx.benchmark.inMemoryTrace
 import androidx.benchmark.macro.perfetto.server.PerfettoHttpServer
 import java.io.File
@@ -129,7 +132,29 @@
     ): T {
         loadTraceImpl(trace.path)
         // TODO: unload trace after block
-        return block.invoke(Session(this))
+        try {
+            return block.invoke(Session(this))
+        } catch (t: Throwable) {
+            // TODO: move this behavior to an extension function in benchmark when
+            //  this class moves out of benchmark group
+            // TODO: consider a label argument to control logging like this in the success case as
+            //  well, which lets us get rid of FileLinkingRule (which doesn't work well anyway)
+            if (trace.path.startsWith(Outputs.outputDirectory.absolutePath)) {
+                // only link trace with failure to Studio if it's an output file
+                InstrumentationResults.instrumentationReport {
+                    val label = "Trace with processing error: ${t.message?.take(50)?.trim()}..."
+                    reportSummaryToIde(
+                        profilerResults = listOf(
+                            Profiler.ResultFile(
+                                label = label,
+                                absolutePath = trace.path
+                            )
+                        )
+                    )
+                }
+            }
+            throw t
+        }
     }
 
     /**
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/perfetto/Slice.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/perfetto/Slice.kt
index fd823e9..7614683 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/perfetto/Slice.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/perfetto/Slice.kt
@@ -25,7 +25,20 @@
     val dur: Long
 ) {
     val endTs: Long = ts + dur
-    val frameId = name.substringAfterLast(" ").toIntOrNull()
+
+    val frameId: Int?
+
+    init {
+        val firstSpaceIndex = name.indexOf(" ")
+        frameId = if (firstSpaceIndex >= 0) {
+            // if see a space, check for id from end of first space to next space (or end of String)
+            val secondSpaceIndex = name.indexOf(" ", firstSpaceIndex + 1)
+            val endFrameIdIndex = if (secondSpaceIndex < 0) name.length else secondSpaceIndex
+            name.substring(firstSpaceIndex + 1, endFrameIdIndex).toIntOrNull()
+        } else {
+            null
+        }
+    }
 
     fun contains(targetTs: Long): Boolean {
         return targetTs >= ts && targetTs <= (ts + dur)
diff --git a/buildSrc/jetpad-integration/build.gradle b/buildSrc/jetpad-integration/build.gradle
index 6941452..e1c0559 100644
--- a/buildSrc/jetpad-integration/build.gradle
+++ b/buildSrc/jetpad-integration/build.gradle
@@ -4,6 +4,10 @@
     sourceCompatibility = JavaVersion.VERSION_11
     targetCompatibility = JavaVersion.VERSION_11
 }
+project.tasks.withType(Jar).configureEach { task ->
+    task.reproducibleFileOrder = true
+    task.preserveFileTimestamps = false
+}
 
 apply from: "../out.gradle"
 init.chooseBuildSrcBuildDir()
diff --git a/buildSrc/shared.gradle b/buildSrc/shared.gradle
index 0c85b7a..b135cf6 100644
--- a/buildSrc/shared.gradle
+++ b/buildSrc/shared.gradle
@@ -26,7 +26,7 @@
     targetCompatibility = JavaVersion.VERSION_17
 }
 
-project.tasks.withType(Jar) { task ->
+project.tasks.withType(Jar).configureEach { task ->
     task.reproducibleFileOrder = true
     task.preserveFileTimestamps = false
 }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInfoAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInfoAdapter.kt
index 05fed81..7b21869 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInfoAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInfoAdapter.kt
@@ -40,6 +40,7 @@
 import androidx.camera.camera2.pipe.integration.impl.CameraProperties
 import androidx.camera.camera2.pipe.integration.impl.DeviceInfoLogger
 import androidx.camera.camera2.pipe.integration.impl.FocusMeteringControl
+import androidx.camera.camera2.pipe.integration.internal.CameraFovInfo
 import androidx.camera.camera2.pipe.integration.interop.Camera2CameraInfo
 import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
 import androidx.camera.core.CameraInfo
@@ -83,6 +84,7 @@
     private val cameraQuirks: CameraQuirks,
     private val encoderProfilesProviderAdapter: EncoderProfilesProviderAdapter,
     private val streamConfigurationMapCompat: StreamConfigurationMapCompat,
+    private val cameraFovInfo: CameraFovInfo,
 ) : CameraInfoInternal {
     init { DeviceInfoLogger.logDeviceInfo(cameraProperties) }
 
@@ -228,6 +230,19 @@
             CONTROL_VIDEO_STABILIZATION_MODE_ON)
     }
 
+    override fun getIntrinsicZoomRatio(): Float {
+        var intrinsicZoomRatio = CameraInfo.INTRINSIC_ZOOM_RATIO_UNKNOWN
+        try {
+            intrinsicZoomRatio =
+                cameraFovInfo.getDefaultCameraDefaultViewAngleDegrees().toFloat() /
+                    cameraFovInfo.getDefaultViewAngleDegrees().toFloat()
+        } catch (e: Exception) {
+            Log.error(e) { "Failed to get the intrinsic zoom ratio" }
+        }
+
+        return intrinsicZoomRatio
+    }
+
     private fun profileSetToDynamicRangeSet(profileSet: Set<Long>): Set<DynamicRange> {
         return profileSet.map { profileToDynamicRange(it) }.toSet()
     }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInternalAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInternalAdapter.kt
index 5020ead..351a853 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInternalAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInternalAdapter.kt
@@ -53,7 +53,7 @@
 ) : CameraInternal {
     private val cameraId = config.cameraId
     private var coreCameraConfig: androidx.camera.core.impl.CameraConfig =
-        CameraConfigs.emptyConfig()
+        CameraConfigs.defaultConfig()
     private val debugId = cameraAdapterIds.incrementAndGet()
 
     init {
@@ -126,7 +126,7 @@
     }
 
     override fun setExtendedConfig(cameraConfig: androidx.camera.core.impl.CameraConfig?) {
-        coreCameraConfig = cameraConfig ?: CameraConfigs.emptyConfig()
+        coreCameraConfig = cameraConfig ?: CameraConfigs.defaultConfig()
     }
 
     override fun toString(): String = "CameraInternalAdapter<$cameraId>"
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/CameraFovInfo.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/CameraFovInfo.kt
new file mode 100644
index 0000000..1f60e1e
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/CameraFovInfo.kt
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.internal
+
+import android.graphics.Rect
+import android.hardware.camera2.CameraCharacteristics
+import android.util.Size
+import android.util.SizeF
+import androidx.annotation.IntRange
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraDevices
+import androidx.camera.camera2.pipe.CameraMetadata
+import androidx.camera.camera2.pipe.integration.config.CameraScope
+import androidx.camera.camera2.pipe.integration.impl.CameraProperties
+import androidx.camera.core.impl.utils.TransformUtils
+import androidx.core.util.Preconditions
+import javax.inject.Inject
+import kotlin.math.atan
+
+@RequiresApi(21)
+@CameraScope
+class CameraFovInfo @Inject constructor(
+    private val cameraDevices: CameraDevices,
+    private val cameraProperties: CameraProperties,
+) {
+    /**
+     * Gets the default focal length from a [CameraMetadata].
+     *
+     * If the camera is a logical camera that consists of multiple physical cameras, the
+     * default focal length is the focal length of the physical camera that produces image at
+     * zoom ratio `1.0`.
+     *
+     * @throws NullPointerException If any of the required [CameraCharacteristics] is not available.
+     *
+     * @throws IllegalStateException If [CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS] is
+     * empty.
+     */
+    private fun getDefaultFocalLength(
+        cameraMetadata: CameraMetadata = cameraProperties.metadata
+    ): Float {
+        val focalLengths: FloatArray = Preconditions.checkNotNull(
+            cameraMetadata[CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS],
+            "The focal lengths can not be empty."
+        )
+
+        Preconditions.checkState(
+            focalLengths.isNotEmpty(),
+            "The focal lengths can not be empty."
+        )
+
+        // Assume the first focal length is the default focal length. This will not be true if the
+        // camera is a logical camera consist of multiple physical cameras and reports multiple
+        // focal lengths. However for this kind of cameras, it's suggested to use zoom ratio to
+        // do optical zoom.
+        return focalLengths[0]
+    }
+
+    /**
+     * Gets the length of the horizontal side of the sensor.
+     *
+     * The horizontal side is the width of the sensor size after rotated by the sensor
+     * orientation.
+     *
+     * @throws NullPointerException If any of the required [CameraCharacteristics] is not available.
+     */
+    private fun getSensorHorizontalLength(
+        cameraMetadata: CameraMetadata = cameraProperties.metadata
+    ): Float {
+        var sensorSize: SizeF = Preconditions.checkNotNull(
+            cameraMetadata[CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE],
+            "The sensor size can't be null."
+        )
+
+        val activeArrayRect: Rect = Preconditions.checkNotNull(
+            cameraMetadata[CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE],
+            "The sensor orientation can't be null."
+        )
+
+        var pixelArraySize: Size = Preconditions.checkNotNull(
+            cameraMetadata[CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE],
+            "The active array size can't be null."
+        )
+
+        val sensorOrientation: Int = Preconditions.checkNotNull(
+            cameraMetadata[CameraCharacteristics.SENSOR_ORIENTATION],
+            "The pixel array size can't be null."
+        )
+
+        var activeArraySize = TransformUtils.rectToSize(activeArrayRect)
+        if (TransformUtils.is90or270(sensorOrientation)) {
+            sensorSize = TransformUtils.reverseSizeF(sensorSize)
+            activeArraySize = TransformUtils.reverseSize(activeArraySize)
+            pixelArraySize = TransformUtils.reverseSize(pixelArraySize)
+        }
+        return sensorSize.width * activeArraySize.width / pixelArraySize.width
+    }
+
+    /**
+     * Calculates view angle by focal length and sensor length.
+     *
+     * The returned view angle is inexact and might not be hundred percent accurate comparing
+     * to the output image.
+     *
+     * The returned view angle should between 0 and 360.
+     *
+     * @throws IllegalArgumentException If the provided focal length or sensor length is not
+     * positive, or results in an invalid view angle.
+     */
+    @IntRange(from = 0, to = 360)
+    private fun focalLengthToViewAngleDegrees(focalLength: Float, sensorLength: Float): Int {
+        Preconditions.checkArgument(focalLength > 0, "Focal length should be positive.")
+        Preconditions.checkArgument(sensorLength > 0, "Sensor length should be positive.")
+        val viewAngleDegrees = Math.toDegrees(
+            2 * atan((sensorLength / (2 * focalLength)).toDouble())
+        ).toInt()
+        Preconditions.checkArgumentInRange(
+            viewAngleDegrees,
+            0,
+            360,
+            "The provided focal length and sensor length result in an invalid view" +
+                " angle degrees."
+        )
+        return viewAngleDegrees
+    }
+
+    /**
+     * Gets the angle of view of the current camera on the device.
+     *
+     * @throws IllegalStateException If a valid view angle could not be found.
+     */
+    @Throws(IllegalStateException::class)
+    fun getDefaultViewAngleDegrees(): Int {
+        try {
+            return focalLengthToViewAngleDegrees(
+                getDefaultFocalLength(),
+                getSensorHorizontalLength()
+            )
+        } catch (e: Exception) {
+            throw IllegalStateException("Failed to get a valid view angle", e)
+        }
+    }
+
+    /**
+     * Gets the angle of view of the default camera on the device.
+     *
+     *
+     * The default cameras is the camera selected by
+     * [androidx.camera.core.CameraSelector.DEFAULT_FRONT_CAMERA] or
+     * [androidx.camera.core.CameraSelector.DEFAULT_BACK_CAMERA]
+     * depending on the specified lens facing.
+     *
+     * @throws IllegalStateException If a valid view angle could not be found.
+     */
+    @Throws(IllegalStateException::class)
+    fun getDefaultCameraDefaultViewAngleDegrees(): Int {
+        try {
+            val cameraIds = Preconditions.checkNotNull(
+                cameraDevices.awaitCameraIds(),
+                "Failed to get available camera IDs",
+            )
+
+            cameraIds.forEach { cameraId ->
+                val cameraMetadata = Preconditions.checkNotNull(
+                    cameraDevices.awaitCameraMetadata(cameraId),
+                    "Failed to get CameraMetadata for $cameraId",
+                )
+                val cameraLensFacing = Preconditions.checkNotNull(
+                    cameraMetadata[CameraCharacteristics.LENS_FACING],
+                    "Failed to get CameraCharacteristics.LENS_FACING for $cameraId"
+                )
+                val currentLensFacing = Preconditions.checkNotNull(
+                    cameraProperties.metadata[CameraCharacteristics.LENS_FACING],
+                    "Failed to get the required LENS_FACING" +
+                        " for ${cameraProperties.cameraId}"
+                )
+                if (cameraLensFacing == currentLensFacing) {
+                    return focalLengthToViewAngleDegrees(
+                        getDefaultFocalLength(cameraMetadata),
+                        getSensorHorizontalLength(cameraMetadata)
+                    )
+                }
+            }
+
+            throw IllegalStateException(
+                "Could not find the default camera for ${cameraProperties.cameraId}"
+            )
+        } catch (e: Exception) {
+            throw IllegalStateException("Failed to get a valid view angle", e)
+        }
+    }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfo.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfo.kt
index aafb109..cf910d3 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfo.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfo.kt
@@ -24,6 +24,7 @@
 import androidx.camera.camera2.pipe.integration.impl.CameraProperties
 import androidx.camera.core.CameraInfo
 import androidx.camera.core.impl.CameraInfoInternal
+import androidx.core.util.Preconditions
 
 /**
  * An interface for retrieving Camera2-related camera information.
@@ -31,7 +32,7 @@
 @ExperimentalCamera2Interop
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 class Camera2CameraInfo private constructor(
-    internal val cameraProperties: CameraProperties,
+    private val cameraProperties: CameraProperties,
 ) {
 
     /**
@@ -72,31 +73,6 @@
 
     fun getCameraId(): String = cameraProperties.cameraId.value
 
-    /**
-     * Returns a map consisting of the camera ids and the
-     * [android.hardware.camera2.CameraCharacteristics]s.
-     *
-     * For every camera, the map contains at least the CameraCharacteristics for the camera id.
-     * If the camera is logical camera, it will also contain associated physical camera ids and
-     * their CameraCharacteristics.
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY)
-    fun getCameraCharacteristicsMap(): Map<String, CameraCharacteristics> {
-        return buildMap {
-            put(
-                cameraProperties.cameraId.value,
-                cameraProperties.metadata.unwrapAs(CameraCharacteristics::class)!!
-            )
-            for (cameraId in cameraProperties.metadata.physicalCameraIds) {
-                put(
-                    cameraId.value,
-                    cameraProperties.metadata.awaitPhysicalMetadata(cameraId)
-                        .unwrapAs(CameraCharacteristics::class)!!
-                )
-            }
-        }
-    }
-
     companion object {
 
         /**
@@ -111,10 +87,11 @@
         @JvmStatic
         fun from(@Suppress("UNUSED_PARAMETER") cameraInfo: CameraInfo): Camera2CameraInfo {
             var cameraInfoImpl = (cameraInfo as CameraInfoInternal).implementation
-            require(cameraInfoImpl is CameraInfoAdapter) {
+            Preconditions.checkArgument(
+                cameraInfoImpl is CameraInfoAdapter,
                 "CameraInfo doesn't contain Camera2 implementation."
-            }
-            return cameraInfoImpl.camera2CameraInfo
+            )
+            return (cameraInfoImpl as CameraInfoAdapter).camera2CameraInfo
         }
 
         /**
@@ -123,29 +100,5 @@
         @RestrictTo(RestrictTo.Scope.LIBRARY)
         @JvmStatic
         fun create(cameraProperties: CameraProperties) = Camera2CameraInfo(cameraProperties)
-
-        /**
-         * Returns the [android.hardware.camera2.CameraCharacteristics] for this camera.
-         *
-         * The CameraCharacteristics will be the ones that would be obtained by
-         * [android.hardware.camera2.CameraManager.getCameraCharacteristics]. The
-         * CameraCharacteristics that are retrieved are not static and can change depending on the
-         * current internal configuration of the camera.
-         *
-         * @param cameraInfo The [CameraInfo] to extract the CameraCharacteristics from.
-         * @throws IllegalStateException if the camera info does not contain the camera 2
-         *                               characteristics(e.g., if CameraX was not initialized with a
-         *                               [androidx.camera.camera2.Camera2Config]).
-         */
-        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-        @JvmStatic
-        fun extractCameraCharacteristics(cameraInfo: CameraInfo): CameraCharacteristics {
-            var cameraInfoImpl = (cameraInfo as CameraInfoInternal).implementation
-            require(cameraInfoImpl is CameraInfoAdapter) {
-                "CameraInfo doesn't contain Camera2 implementation."
-            }
-            return cameraInfoImpl.camera2CameraInfo
-                .cameraProperties.metadata.unwrapAs(CameraCharacteristics::class)!!
-        }
     }
 }
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraInfoAdapterTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraInfoAdapterTest.kt
index d352ea7..716e8e8 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraInfoAdapterTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraInfoAdapterTest.kt
@@ -16,13 +16,18 @@
 
 package androidx.camera.camera2.pipe.integration.adapter
 
+import android.graphics.Rect
 import android.hardware.camera2.CameraCharacteristics
 import android.hardware.camera2.CameraCharacteristics.CONTROL_VIDEO_STABILIZATION_MODE_OFF
 import android.hardware.camera2.CameraCharacteristics.CONTROL_VIDEO_STABILIZATION_MODE_ON
 import android.hardware.camera2.CameraCharacteristics.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION
+import android.hardware.camera2.CameraMetadata
 import android.os.Build
 import android.util.Range
 import android.util.Size
+import android.util.SizeF
+import androidx.camera.camera2.pipe.CameraBackendId
+import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.integration.impl.ZoomControl
 import androidx.camera.camera2.pipe.integration.internal.DOLBY_VISION_10B_UNCONSTRAINED
 import androidx.camera.camera2.pipe.integration.internal.HLG10_UNCONSTRAINED
@@ -31,6 +36,7 @@
 import androidx.camera.camera2.pipe.integration.testing.FakeCameraProperties
 import androidx.camera.camera2.pipe.integration.testing.FakeUseCaseCamera
 import androidx.camera.camera2.pipe.integration.testing.FakeZoomCompat
+import androidx.camera.camera2.pipe.testing.FakeCameraDevices
 import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
 import androidx.camera.core.CameraInfo
 import androidx.camera.core.DynamicRange
@@ -67,6 +73,41 @@
     @get:Rule
     val dispatcherRule = MainDispatcherRule(MoreExecutors.directExecutor().asCoroutineDispatcher())
 
+    private val defaultCameraId = "0"
+    private val defaultCameraCharacteristics = mapOf(
+        CameraCharacteristics.LENS_FACING to CameraMetadata.LENS_FACING_BACK,
+        CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS to floatArrayOf(1.0f),
+        CameraCharacteristics.SENSOR_ORIENTATION to 0,
+        CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE to Size(10, 10),
+        CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE to Rect(0, 0, 10, 10),
+        CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE to SizeF(10f, 10f),
+    )
+    private val defaultCameraProperties = FakeCameraProperties(
+        FakeCameraMetadata(characteristics = defaultCameraCharacteristics)
+    )
+
+    private val telephotoCameraId = "2"
+
+    // Only SENSOR_INFO_PHYSICAL_SIZE has been made less wider and everything else kept the same
+    private val telephotoCameraProperties = FakeCameraProperties(
+        FakeCameraMetadata(
+            characteristics = defaultCameraCharacteristics.toMutableMap().apply {
+                put(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE, SizeF(1f, 10f))
+            }
+        )
+    )
+
+    private val ultraWideCameraId = "2"
+
+    // Only SENSOR_INFO_PHYSICAL_SIZE has been made wider and everything else kept the same
+    private val ultraWideCameraProperties = FakeCameraProperties(
+        FakeCameraMetadata(
+            characteristics = defaultCameraCharacteristics.toMutableMap().apply {
+                put(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE, SizeF(100f, 10f))
+            }
+        )
+    )
+
     @Test
     fun getSupportedResolutions() {
         // Act.
@@ -391,4 +432,186 @@
 
         assertThat(cameraInfo.querySupportedDynamicRanges(emptySet())).isEmpty()
     }
+
+    @Test
+    fun intrinsicZoomRatioIsLessThan1_whenSensorHorizontalLengthWiderThanDefault() {
+        val cameraInfo: CameraInfoInternal = createCameraInfoAdapter(
+            cameraId = CameraId(ultraWideCameraId),
+            cameraProperties = ultraWideCameraProperties,
+            cameraDevices = FakeCameraDevices(
+                defaultCameraBackendId = CameraBackendId(defaultCameraId),
+                concurrentCameraBackendIds = emptySet(),
+                cameraMetadataMap = mapOf(
+                    CameraBackendId(defaultCameraId) to listOf(defaultCameraProperties.metadata),
+                    CameraBackendId(ultraWideCameraId) to
+                        listOf(ultraWideCameraProperties.metadata),
+                )
+            )
+        )
+
+        assertThat(cameraInfo.intrinsicZoomRatio).isLessThan(1)
+    }
+
+    @Test
+    fun intrinsicZoomRatioIsGreaterThan1_whenSensorHorizontalLengthSmallerThanDefault() {
+        val cameraInfo: CameraInfoInternal = createCameraInfoAdapter(
+            cameraId = CameraId(telephotoCameraId),
+            cameraProperties = telephotoCameraProperties,
+            cameraDevices = FakeCameraDevices(
+                defaultCameraBackendId = CameraBackendId(defaultCameraId),
+                concurrentCameraBackendIds = emptySet(),
+                cameraMetadataMap = mapOf(
+                    CameraBackendId(defaultCameraId) to listOf(defaultCameraProperties.metadata),
+                    CameraBackendId(telephotoCameraId) to
+                        listOf(telephotoCameraProperties.metadata),
+                )
+            )
+        )
+
+        assertThat(cameraInfo.intrinsicZoomRatio).isGreaterThan(1)
+    }
+
+    @Test
+    fun intrinsicZoomRatioIsUnknown_whenNoLensFacingInfo() {
+        val cameraProperties = FakeCameraProperties(
+            FakeCameraMetadata(
+                characteristics = defaultCameraCharacteristics.toMutableMap().apply {
+                    remove(CameraCharacteristics.LENS_FACING)
+                }
+            )
+        )
+        val cameraInfo: CameraInfoInternal = createCameraInfoAdapter(
+            cameraId = CameraId(defaultCameraId),
+            cameraProperties = cameraProperties,
+            cameraDevices = FakeCameraDevices(
+                defaultCameraBackendId = CameraBackendId(defaultCameraId),
+                concurrentCameraBackendIds = emptySet(),
+                cameraMetadataMap = mapOf(
+                    CameraBackendId(defaultCameraId) to listOf(cameraProperties.metadata),
+                )
+            )
+        )
+
+        assertThat(cameraInfo.intrinsicZoomRatio).isEqualTo(CameraInfo.INTRINSIC_ZOOM_RATIO_UNKNOWN)
+    }
+
+    @Test
+    fun intrinsicZoomRatioIsUnknown_whenNoLensFocalLengthInfo() {
+        val cameraProperties = FakeCameraProperties(
+            FakeCameraMetadata(
+                characteristics = defaultCameraCharacteristics.toMutableMap().apply {
+                    remove(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS)
+                }
+            )
+        )
+        val cameraInfo: CameraInfoInternal = createCameraInfoAdapter(
+            cameraId = CameraId(defaultCameraId),
+            cameraProperties = cameraProperties,
+            cameraDevices = FakeCameraDevices(
+                defaultCameraBackendId = CameraBackendId(defaultCameraId),
+                concurrentCameraBackendIds = emptySet(),
+                cameraMetadataMap = mapOf(
+                    CameraBackendId(defaultCameraId) to listOf(cameraProperties.metadata),
+                )
+            )
+        )
+
+        assertThat(cameraInfo.intrinsicZoomRatio).isEqualTo(CameraInfo.INTRINSIC_ZOOM_RATIO_UNKNOWN)
+    }
+
+    @Test
+    fun intrinsicZoomRatioIsUnknown_whenNoSensorOrientationInfo() {
+        val cameraProperties = FakeCameraProperties(
+            FakeCameraMetadata(
+                characteristics = defaultCameraCharacteristics.toMutableMap().apply {
+                    remove(CameraCharacteristics.SENSOR_ORIENTATION)
+                }
+            )
+        )
+        val cameraInfo: CameraInfoInternal = createCameraInfoAdapter(
+            cameraId = CameraId(defaultCameraId),
+            cameraProperties = cameraProperties,
+            cameraDevices = FakeCameraDevices(
+                defaultCameraBackendId = CameraBackendId(defaultCameraId),
+                concurrentCameraBackendIds = emptySet(),
+                cameraMetadataMap = mapOf(
+                    CameraBackendId(defaultCameraId) to listOf(cameraProperties.metadata),
+                )
+            )
+        )
+
+        assertThat(cameraInfo.intrinsicZoomRatio).isEqualTo(CameraInfo.INTRINSIC_ZOOM_RATIO_UNKNOWN)
+    }
+
+    @Test
+    fun intrinsicZoomRatioIsUnknown_whenNoSensorPixelArraySizeInfo() {
+        val cameraProperties = FakeCameraProperties(
+            FakeCameraMetadata(
+                characteristics = defaultCameraCharacteristics.toMutableMap().apply {
+                    remove(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE)
+                }
+            )
+        )
+        val cameraInfo: CameraInfoInternal = createCameraInfoAdapter(
+            cameraId = CameraId(defaultCameraId),
+            cameraProperties = cameraProperties,
+            cameraDevices = FakeCameraDevices(
+                defaultCameraBackendId = CameraBackendId(defaultCameraId),
+                concurrentCameraBackendIds = emptySet(),
+                cameraMetadataMap = mapOf(
+                    CameraBackendId(defaultCameraId) to listOf(cameraProperties.metadata),
+                )
+            )
+        )
+
+        assertThat(cameraInfo.intrinsicZoomRatio).isEqualTo(CameraInfo.INTRINSIC_ZOOM_RATIO_UNKNOWN)
+    }
+
+    @Test
+    fun intrinsicZoomRatioIsUnknown_whenNoSensorActiveArraySizeInfo() {
+        val cameraProperties = FakeCameraProperties(
+            FakeCameraMetadata(
+                characteristics = defaultCameraCharacteristics.toMutableMap().apply {
+                    remove(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE)
+                }
+            )
+        )
+        val cameraInfo: CameraInfoInternal = createCameraInfoAdapter(
+            cameraId = CameraId(defaultCameraId),
+            cameraProperties = cameraProperties,
+            cameraDevices = FakeCameraDevices(
+                defaultCameraBackendId = CameraBackendId(defaultCameraId),
+                concurrentCameraBackendIds = emptySet(),
+                cameraMetadataMap = mapOf(
+                    CameraBackendId(defaultCameraId) to listOf(cameraProperties.metadata),
+                )
+            )
+        )
+
+        assertThat(cameraInfo.intrinsicZoomRatio).isEqualTo(CameraInfo.INTRINSIC_ZOOM_RATIO_UNKNOWN)
+    }
+
+    @Test
+    fun intrinsicZoomRatioIsUnknown_whenNoSensorPhysicalSizeInfo() {
+        val cameraProperties = FakeCameraProperties(
+            FakeCameraMetadata(
+                characteristics = defaultCameraCharacteristics.toMutableMap().apply {
+                    remove(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE)
+                }
+            )
+        )
+        val cameraInfo: CameraInfoInternal = createCameraInfoAdapter(
+            cameraId = CameraId(defaultCameraId),
+            cameraProperties = cameraProperties,
+            cameraDevices = FakeCameraDevices(
+                defaultCameraBackendId = CameraBackendId(defaultCameraId),
+                concurrentCameraBackendIds = emptySet(),
+                cameraMetadataMap = mapOf(
+                    CameraBackendId(defaultCameraId) to listOf(cameraProperties.metadata),
+                )
+            )
+        )
+
+        assertThat(cameraInfo.intrinsicZoomRatio).isEqualTo(CameraInfo.INTRINSIC_ZOOM_RATIO_UNKNOWN)
+    }
 }
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraInfoAdapterCreator.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraInfoAdapterCreator.kt
index 21c789a..0edc033 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraInfoAdapterCreator.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraInfoAdapterCreator.kt
@@ -22,6 +22,8 @@
 import android.util.Range
 import android.util.Size
 import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraBackendId
+import androidx.camera.camera2.pipe.CameraDevices
 import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.integration.adapter.CameraControlStateAdapter
 import androidx.camera.camera2.pipe.integration.adapter.CameraInfoAdapter
@@ -42,6 +44,8 @@
 import androidx.camera.camera2.pipe.integration.impl.TorchControl
 import androidx.camera.camera2.pipe.integration.impl.UseCaseThreads
 import androidx.camera.camera2.pipe.integration.impl.ZoomControl
+import androidx.camera.camera2.pipe.integration.internal.CameraFovInfo
+import androidx.camera.camera2.pipe.testing.FakeCameraDevices
 import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
 import androidx.camera.core.impl.ImageFormatConstants
 import com.google.common.util.concurrent.MoreExecutors
@@ -96,6 +100,14 @@
             cameraId
         ),
         zoomControl: ZoomControl = this.zoomControl,
+        cameraDevices: CameraDevices = FakeCameraDevices(
+            defaultCameraBackendId = CameraBackendId(cameraId.value),
+            concurrentCameraBackendIds = emptySet(),
+            cameraMetadataMap = mapOf(
+                CameraBackendId(cameraId.value) to listOf(cameraProperties.metadata)
+            )
+        )
+
     ): CameraInfoAdapter {
         val fakeUseCaseCamera = FakeUseCaseCamera()
         val fakeStreamConfigurationMap = StreamConfigurationMapCompat(
@@ -137,6 +149,7 @@
             fakeCameraQuirks,
             EncoderProfilesProviderAdapter(cameraId.value),
             fakeStreamConfigurationMap,
+            CameraFovInfo(cameraDevices, cameraProperties),
         )
     }
 }
diff --git a/camera/camera-camera2-pipe/build.gradle b/camera/camera-camera2-pipe/build.gradle
index d9a7bf3..80fad89 100644
--- a/camera/camera-camera2-pipe/build.gradle
+++ b/camera/camera-camera2-pipe/build.gradle
@@ -39,6 +39,7 @@
     testImplementation(libs.junit)
     testImplementation(libs.truth)
     testImplementation(libs.robolectric)
+    testImplementation(libs.kotlinTest)
     testImplementation(libs.kotlinCoroutinesTest)
     testImplementation(libs.mockitoCore4)
     testImplementation(libs.mockitoKotlin4)
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Requests.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Requests.kt
index 1aad2bf..278646b 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Requests.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Requests.kt
@@ -26,6 +26,7 @@
 import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
 import androidx.camera.camera2.pipe.core.Debug
+import androidx.camera.camera2.pipe.core.Log
 
 /**
  * A [RequestNumber] is an artificial identifier that is created for each request that is submitted
@@ -361,7 +362,17 @@
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 fun CaptureRequest.Builder.writeParameter(key: Any?, value: Any?) {
     if (key != null && key is CaptureRequest.Key<*>) {
-        @Suppress("UNCHECKED_CAST") this.set(key as CaptureRequest.Key<Any>, value)
+        try {
+            @Suppress("UNCHECKED_CAST") this.set(key as CaptureRequest.Key<Any>, value)
+        } catch (e: IllegalArgumentException) {
+            // Setting keys on CaptureRequest.Builder can fail if the key is defined on some
+            // OS versions, but not on others. Log and ignore these kinds of failures.
+            //
+            // See b/309518353 for an example failure.
+            Log.warn(e) {
+                "Failed to set [${key.name}: $value] on CaptureRequest.Builder"
+            }
+        }
     }
 }
 
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/CoroutineMutex.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/CoroutineMutex.kt
new file mode 100644
index 0000000..1ed414fe
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/CoroutineMutex.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.core
+
+import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
+import kotlin.coroutines.intrinsics.intercepted
+import kotlin.coroutines.intrinsics.startCoroutineUninterceptedOrReturn
+import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
+import kotlin.coroutines.resume
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.ensureActive
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.Mutex
+
+/**
+ * [CoroutineMutex] is a shared [Mutex] instance with extension functions to allow callers to lock
+ * the mutex from a non-coroutine function, while ensuring that the locked execution block is
+ * always suspended (Which avoids synchronously running code that should be executed on a different
+ * thread). This guaranteed suspension, plus the additional extension functions to make it easier
+ * to correctly lock and run suspending code from a non-suspending function, is the primary
+ * difference from using a [Mutex] object directly.
+ *
+ * This should not be used to run long running operations, since the lock will be held for the
+ * entire duration. In addition, kotlin [Mutex] objects are non-reentrant, unlike standard java
+ * `synchronized` locks.
+ */
+class CoroutineMutex {
+    internal val mutex = Mutex()
+}
+
+/**
+ * Execute the provided [block] within the current [CoroutineMutex] while ensuring that only one
+ * operation is executed at a time.
+ */
+fun <T> CoroutineMutex.withLockAsync(
+    scope: CoroutineScope,
+    block: suspend CoroutineScope.() -> T
+): Deferred<T> {
+    // Using CoroutineStart.UNDISPATCHED ensures that the mutex.lock call is invoked synchronously
+    // on the current thread. This prevents two operations from being mis-ordered since the lock
+    // is acquired before the block is scheduled for execution.
+    return scope.async(start = CoroutineStart.UNDISPATCHED) {
+        ensureActive()
+
+        // The block is called within a new CoroutineScope, while holding the lock. This ensures
+        // that any child coroutines started via `block` are completed before `lock` gets released
+        // and the next block starts, which includes other async/launch calls invoked on the
+        // scope.
+        mutex.withLockAndSuspend { coroutineScope(block) }
+    }
+}
+
+/**
+ * Execute the provided [block] after acquiring a lock to the [CoroutineMutex] in the provided
+ * [CoroutineScope].
+ */
+fun CoroutineMutex.withLockLaunch(
+    scope: CoroutineScope,
+    block: suspend CoroutineScope.() -> Unit
+): Job {
+    // Using CoroutineStart.UNDISPATCHED ensures that the mutex.lock call is invoked synchronously
+    // on the current thread. This prevents two operations from being mis-ordered since the lock
+    // is acquired before the block is scheduled for execution.
+    return scope.launch(start = CoroutineStart.UNDISPATCHED) {
+        ensureActive()
+
+        // The block is called within a new CoroutineScope, while holding the lock. This ensures
+        // that any child coroutines started via `block` are completed before `lock` gets released
+        // and the next block starts.
+        mutex.withLockAndSuspend { coroutineScope(block) }
+    }
+}
+
+/**
+ * This function allows the implementation of [Mutex.lockAndSuspend] to call the overload of
+ * [startCoroutineUninterceptedOrReturn] that takes a receiver, which saves an allocation and allows
+ * the compiler to skip the coroutine state machine logic in [Mutex.lockAndSuspend].
+ */
+private suspend fun Mutex.lockWithoutOwner() = lock(owner = null)
+
+/**
+ * Same as [kotlinx.coroutines.sync.withLock], but guarantees that the coroutine is suspended before
+ * the lock is acquired whether or not the lock is locked at the time this function is called.
+ */
+private suspend inline fun <T> Mutex.withLockAndSuspend(action: () -> T): T {
+    lockAndSuspend()
+    try {
+        return action()
+    } finally {
+        unlock()
+    }
+}
+
+/**
+ * Same as [Mutex.lock], but guarantees that the coroutine is *always* suspended before the lock is
+ * acquired, regardless of if the lock is locked at the time this function is called. This ensures
+ * consistent behavior when calling lock from within a [CoroutineStart.UNDISPATCHED] coroutine.
+ */
+private suspend fun Mutex.lockAndSuspend() {
+    val lockFn = Mutex::lockWithoutOwner
+    return suspendCoroutineUninterceptedOrReturn { continuation ->
+        if (lockFn.startCoroutineUninterceptedOrReturn(
+                this,
+                continuation
+            ) !== COROUTINE_SUSPENDED
+        ) {
+            // If the mutex.lock call did *not* suspend (likely because the lock was acquired
+            // immediately), intercept the continuation block, which will schedule it for execution.
+            continuation.intercepted().resume(Unit)
+        }
+
+        // The result is always that the continuation is always suspending.
+        COROUTINE_SUSPENDED
+    }
+}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphProcessor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphProcessor.kt
index 01fc505..fd603e5 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphProcessor.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphProcessor.kt
@@ -35,10 +35,12 @@
 import androidx.camera.camera2.pipe.compat.Camera2Quirks
 import androidx.camera.camera2.pipe.config.CameraGraphScope
 import androidx.camera.camera2.pipe.config.ForCameraGraph
+import androidx.camera.camera2.pipe.core.CoroutineMutex
 import androidx.camera.camera2.pipe.core.Debug
 import androidx.camera.camera2.pipe.core.Log.debug
 import androidx.camera.camera2.pipe.core.Log.warn
 import androidx.camera.camera2.pipe.core.Threads
+import androidx.camera.camera2.pipe.core.withLockLaunch
 import androidx.camera.camera2.pipe.formatForLogs
 import androidx.camera.camera2.pipe.putAllMetadata
 import java.util.concurrent.CountDownLatch
@@ -133,6 +135,7 @@
 ) : GraphProcessor, GraphListener {
     private val lock = Any()
     private val tryStartRepeatingExecutionLock = Any()
+    private val coroutineMutex = CoroutineMutex()
 
     @GuardedBy("lock")
     private val submitQueue: MutableList<List<Request>> = ArrayList()
@@ -275,9 +278,11 @@
             if (closed) return
             repeatingQueue.add(request)
             debug { "startRepeating with ${request.formatForLogs()}" }
-        }
 
-        graphScope.launch(threads.lightweightDispatcher) { tryStartRepeating() }
+            coroutineMutex.withLockLaunch(graphScope) {
+                tryStartRepeating()
+            }
+        }
     }
 
     override fun stopRepeating() {
@@ -287,15 +292,15 @@
             processor = _requestProcessor
             repeatingQueue.clear()
             currentRepeatingRequest = null
-        }
 
-        graphScope.launch(threads.lightweightDispatcher) {
-            Debug.traceStart { "$this#stopRepeating" }
-            // Start with requests that have already been submitted
-            if (processor != null) {
-                synchronized(processor) { processor.stopRepeating() }
+            coroutineMutex.withLockLaunch(graphScope) {
+                Debug.traceStart { "$this#stopRepeating" }
+                // Start with requests that have already been submitted
+                if (processor != null) {
+                    synchronized(processor) { processor.stopRepeating() }
+                }
+                Debug.traceStop()
             }
-            Debug.traceStop()
         }
     }
 
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/core/CoroutineMutexTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/core/CoroutineMutexTest.kt
new file mode 100644
index 0000000..cd4fe23
--- /dev/null
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/core/CoroutineMutexTest.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.core
+
+import android.os.Build
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CancellationException
+import kotlin.test.assertFailsWith
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.robolectric.annotation.Config
+
+@RunWith(JUnit4::class)
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class CoroutineMutexTest {
+    private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
+
+    @Test
+    fun testMultipleSequencesAreIndependent() = runBlocking {
+        val sequence1 = CoroutineMutex()
+        val sequence2 = CoroutineMutex()
+
+        val internalResult =
+            sequence1.withLockAsync(scope) {
+                sequence2.withLockAsync(scope) { 42 }.await()
+            }
+                .await()
+
+        assertThat(internalResult).isEqualTo(42)
+    }
+
+    @Test
+    fun testSequenceOrder() = runBlocking {
+        var counter = 0
+        val sequence = CoroutineMutex()
+
+        val first = sequence.withLockAsync(scope) { ++counter }
+        val second = sequence.withLockAsync(scope) { ++counter }
+        val third = sequence.withLockAsync(scope) { ++counter }
+
+        assertThat(listOf(second, first, third).awaitAll()).containsExactly(2, 1, 3).inOrder()
+    }
+
+    @Test
+    fun testLaunchOnOuterScopeWithinExecuteAsyncDoesNotDeadlock() = runBlocking {
+        var output: Int = -1
+        val sequence = CoroutineMutex()
+
+        coroutineScope {
+            val sharedMutex = Mutex(locked = true)
+
+            sequence.withLockAsync(this) {
+                // Note: The receiver is `this@coroutineScope`. The `sequenceAsync {}` block will return
+                // racing the launched block.
+                this@coroutineScope.launch {
+                    sharedMutex.lock()
+                    // Doesn't throw as no block reentered `sequenceAsync {}`.
+                    sequence.withLockAsync(this) { output = 42 }.await()
+                }
+            }
+                .await()
+
+            sharedMutex.unlock()
+        }
+        assertThat(output).isEqualTo(42)
+    }
+
+    @Test
+    fun testLaunchOrderIndependentOfScope() {
+        var counter = 0
+        val sequence = CoroutineMutex()
+        val scope1 = CoroutineScope(Job() + Dispatchers.Default)
+        val scope2 = CoroutineScope(Job() + Dispatchers.Default)
+
+        val output = mutableListOf<Deferred<Int>>()
+
+        for (i in 0..5000) {
+            if (i % 2 > 0) {
+                output.add(sequence.withLockAsync(scope1) { counter++ })
+            } else {
+                output.add(sequence.withLockAsync(scope2) { counter++ })
+            }
+        }
+
+        runBlocking {
+            assertThat(output.awaitAll()).containsExactlyElementsIn(0.rangeTo(5000)).inOrder()
+        }
+    }
+
+    @Test
+    fun testCancelledCoroutineDropsFromSequence() {
+        var counter = 0
+        val sequence = CoroutineMutex()
+        val mutex = Mutex(locked = true)
+
+        val first = sequence.withLockAsync(scope) { mutex.withLock { ++counter } }
+        val second = sequence.withLockAsync(scope) { mutex.withLock { ++counter } }
+        val third = sequence.withLockAsync(scope) { mutex.withLock { ++counter } }
+
+        second.cancel()
+
+        mutex.unlock()
+        runBlocking {
+            assertFailsWith(CancellationException::class) { second.await() }
+            assertThat(first.await()).isEqualTo(1)
+            assertThat(third.await()).isEqualTo(2)
+        }
+    }
+
+    @Test
+    fun testFailedCoroutineDropsFromSequenceWithoutAffectingLaterBlocks() {
+        class ExpectedException : Exception()
+
+        var counter = 0
+        val sequence = CoroutineMutex()
+
+        val first = sequence.withLockAsync(scope) { ++counter }
+        val second = sequence.withLockAsync(scope) { throw ExpectedException() }
+        val third = sequence.withLockAsync(scope) { ++counter }
+
+        runBlocking {
+            assertThat(first.await()).isEqualTo(1)
+            assertFailsWith(ExpectedException::class) { second.await() }
+            assertThat(third.await()).isEqualTo(2)
+        }
+    }
+}
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2ImplCameraXTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2ImplCameraXTest.java
index 1cb826b..a4656f8 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2ImplCameraXTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2ImplCameraXTest.java
@@ -51,6 +51,7 @@
 import androidx.camera.core.internal.CameraUseCaseAdapter;
 import androidx.camera.testing.impl.CameraUtil;
 import androidx.camera.testing.impl.CameraXUtil;
+import androidx.camera.testing.impl.WakelockEmptyActivityRule;
 import androidx.camera.testing.impl.fakes.FakeLifecycleOwner;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -95,6 +96,9 @@
             new CameraUtil.PreTestCameraIdList(Camera2Config.defaultConfig())
     );
 
+    @Rule
+    public TestRule wakelockEmptyActivityRule = new WakelockEmptyActivityRule();
+
     private CameraDevice.StateCallback mDeviceStateCallback;
     private FakeLifecycleOwner mLifecycle;
 
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/FovDeviceTest.kt b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/FovDeviceTest.kt
deleted file mode 100644
index 8df88c2..0000000
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/FovDeviceTest.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.camera2.internal
-
-import android.content.Context
-import androidx.camera.camera2.Camera2Config
-import androidx.camera.core.CameraInfo
-import androidx.camera.core.CameraSelector
-import androidx.camera.core.impl.CameraInfoInternal
-import androidx.camera.core.internal.CameraUseCaseAdapter
-import androidx.camera.testing.impl.CameraUtil
-import androidx.camera.testing.impl.CameraXUtil
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.filters.SdkSuppress
-import com.google.common.truth.Truth.assertThat
-import java.util.Collections
-import java.util.concurrent.TimeUnit
-import org.junit.After
-import org.junit.Assume.assumeTrue
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-
-private val DEFAULT_CAMERA_ID_GROUP = Collections.unmodifiableSet(setOf("0", "1"))
-
-@RunWith(Parameterized::class)
-@SdkSuppress(minSdkVersion = 21)
-class FovDeviceTest(private val cameraId: String) {
-    private val cameraConfig = Camera2Config.defaultConfig()
-
-    @get:Rule
-    val cameraRule = CameraUtil.grantCameraPermissionAndPreTest(
-        CameraUtil.PreTestCameraIdList(cameraConfig)
-    )
-
-    companion object {
-        @JvmStatic
-        @Parameterized.Parameters(name = "{0}")
-        fun data(): ArrayList<String> {
-            return ArrayList(CameraUtil.getBackwardCompatibleCameraIdListOrThrow())
-        }
-    }
-
-    private lateinit var cameraUseCaseAdapter: CameraUseCaseAdapter
-    private val context: Context = ApplicationProvider.getApplicationContext()
-
-    @Before
-    fun setUp() {
-        CameraXUtil.initialize(
-            context,
-            cameraConfig
-        ).get()
-
-        val cameraSelector = CameraSelector.Builder().addCameraFilter { cameraInfoList ->
-            val filteredList = ArrayList<CameraInfo>()
-            cameraInfoList.forEach { cameraInfo ->
-                if ((cameraInfo as CameraInfoInternal).cameraId == cameraId) {
-                    filteredList.add(cameraInfo)
-                }
-            }
-            filteredList
-        }.build()
-        cameraUseCaseAdapter =
-            CameraUtil.createCameraUseCaseAdapter(context, cameraSelector)
-    }
-
-    @After
-    fun tearDown() {
-        CameraXUtil.shutdown()[10000, TimeUnit.MILLISECONDS]
-    }
-
-    @Test
-    fun intrinsicZoomRatio_greaterThanZero() {
-        assertThat(cameraUseCaseAdapter.cameraInfo.intrinsicZoomRatio).isGreaterThan(0)
-    }
-
-    @Test
-    fun intrinsicZoomRatio_defaultToOne() {
-        assumeTrue(DEFAULT_CAMERA_ID_GROUP.contains(cameraId))
-        assertThat(cameraUseCaseAdapter.cameraInfo.intrinsicZoomRatio).isEqualTo(1.0F)
-    }
-}
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerDeviceTest.kt b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerDeviceTest.kt
index 4dd1132..02378b2 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerDeviceTest.kt
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerDeviceTest.kt
@@ -33,13 +33,8 @@
 import androidx.camera.core.ImageCaptureException
 import androidx.camera.core.ImageProxy
 import androidx.camera.core.Preview
-import androidx.camera.core.impl.CameraConfig
 import androidx.camera.core.impl.CameraInfoInternal
 import androidx.camera.core.impl.CameraThreadConfig
-import androidx.camera.core.impl.Config
-import androidx.camera.core.impl.Identifier
-import androidx.camera.core.impl.MutableOptionsBundle
-import androidx.camera.core.impl.SessionProcessor
 import androidx.camera.core.impl.SurfaceCombination
 import androidx.camera.core.impl.SurfaceConfig
 import androidx.camera.core.impl.utils.executor.CameraXExecutors
@@ -48,6 +43,7 @@
 import androidx.camera.testing.impl.CameraUtil.PreTestCameraIdList
 import androidx.camera.testing.impl.CameraXUtil
 import androidx.camera.testing.impl.SurfaceTextureProvider
+import androidx.camera.testing.impl.fakes.FakeCameraConfig
 import androidx.camera.testing.impl.fakes.FakeSessionProcessor
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.filters.LargeTest
@@ -126,7 +122,14 @@
     @Test
     fun successCaptureImage_whenExtraYuvPrivYuvConfigurationSupported() = runBlocking {
         var cameraSelector = createCameraSelectorById(cameraId)
-        cameraUseCaseAdapter = CameraUtil.createCameraUseCaseAdapter(context, cameraSelector)
+        val fakeCameraConfig = FakeCameraConfig(
+            sessionProcessor = FakeSessionProcessor(
+                inputFormatPreview = null,
+                inputFormatCapture = ImageFormat.YUV_420_888
+            )
+        )
+        cameraUseCaseAdapter = CameraUtil.createCameraUseCaseAdapter(
+            context, cameraSelector, fakeCameraConfig)
         var camera2CameraInfo = Camera2CameraInfo.from(cameraUseCaseAdapter.cameraInfo)
 
         var hardwareLevel: Int? = camera2CameraInfo.getCameraCharacteristic(
@@ -159,11 +162,6 @@
         var preview = Preview.Builder().build()
         var imageCapture = ImageCapture.Builder().build()
         // This will force ImageCapture to use YUV_420_888 to configure capture session.
-        val fakeSessionProcessor = FakeSessionProcessor(
-            inputFormatPreview = null,
-            inputFormatCapture = ImageFormat.YUV_420_888
-        )
-        enableSessionProcessor(cameraUseCaseAdapter, fakeSessionProcessor)
         withContext(Dispatchers.Main) {
             preview.setSurfaceProvider(getSurfaceProvider())
             cameraUseCaseAdapter.addUseCases(Arrays.asList(imageAnalysis, preview, imageCapture))
@@ -185,7 +183,15 @@
     @Test
     fun successCaptureImage_whenExtraYuvYuvYuvConfigurationSupported() = runBlocking {
         var cameraSelector = createCameraSelectorById(cameraId)
-        cameraUseCaseAdapter = CameraUtil.createCameraUseCaseAdapter(context, cameraSelector)
+        // This will force ImageCapture / Preview to use YUV_420_888 to configure capture session.
+        val fakeCameraConfig = FakeCameraConfig(
+            sessionProcessor = FakeSessionProcessor(
+                inputFormatPreview = ImageFormat.YUV_420_888,
+                inputFormatCapture = ImageFormat.YUV_420_888
+            )
+        )
+        cameraUseCaseAdapter = CameraUtil.createCameraUseCaseAdapter(
+            context, cameraSelector, fakeCameraConfig)
         var camera2CameraInfo = Camera2CameraInfo.from(cameraUseCaseAdapter.cameraInfo)
 
         var hardwareLevel: Int? = camera2CameraInfo.getCameraCharacteristic(
@@ -216,13 +222,6 @@
         var imageAnalysis = ImageAnalysis.Builder().build()
         var preview = Preview.Builder().build()
         var imageCapture = ImageCapture.Builder().build()
-
-        // This will force ImageCapture / Preview to use YUV_420_888 to configure capture session.
-        val fakeSessionProcessor = FakeSessionProcessor(
-            inputFormatPreview = ImageFormat.YUV_420_888,
-            inputFormatCapture = ImageFormat.YUV_420_888
-        )
-        enableSessionProcessor(cameraUseCaseAdapter, fakeSessionProcessor)
         withContext(Dispatchers.Main) {
             preview.setSurfaceProvider(getSurfaceProvider())
             cameraUseCaseAdapter.addUseCases(Arrays.asList(imageAnalysis, preview, imageCapture))
@@ -240,31 +239,6 @@
         }
     }
 
-    private fun enableSessionProcessor(
-        cameraUseCaseAdapter: CameraUseCaseAdapter,
-        sessionProcessor: SessionProcessor
-    ) {
-        cameraUseCaseAdapter.setExtendedConfig(object : CameraConfig {
-            override fun getConfig(): Config {
-                return MutableOptionsBundle.create()
-            }
-
-            override fun getCompatibilityId(): Identifier {
-                return Identifier.create(0)
-            }
-
-            override fun getSessionProcessor(
-                valueIfMissing: SessionProcessor?
-            ): SessionProcessor? {
-                return sessionProcessor
-            }
-
-            override fun getSessionProcessor(): SessionProcessor {
-                return sessionProcessor
-            }
-        })
-    }
-
     private fun createCameraSelectorById(id: String): CameraSelector {
         var builder = CameraSelector.Builder()
 
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
index 2a9afab..1a7363d 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
@@ -196,7 +196,7 @@
     private final Set<String> mNotifyStateAttachedSet = new HashSet<>();
 
     @NonNull
-    private CameraConfig mCameraConfig = CameraConfigs.emptyConfig();
+    private CameraConfig mCameraConfig = CameraConfigs.defaultConfig();
     final Object mLock = new Object();
     // mSessionProcessor will be used to transform capture session if non-null.
     @GuardedBy("mLock")
@@ -889,10 +889,7 @@
 
     @Override
     public void setExtendedConfig(@Nullable CameraConfig cameraConfig) {
-        if (cameraConfig == null) {
-            cameraConfig = CameraConfigs.emptyConfig();
-        }
-
+        cameraConfig = cameraConfig != null ? cameraConfig : CameraConfigs.defaultConfig();
         SessionProcessor sessionProcessor = cameraConfig.getSessionProcessor(null);
         mCameraConfig = cameraConfig;
 
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/Camera.java b/camera/camera-core/src/main/java/androidx/camera/core/Camera.java
index 5616fc8..719b4ff 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/Camera.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/Camera.java
@@ -17,7 +17,6 @@
 package androidx.camera.core;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.camera.core.impl.CameraConfig;
@@ -76,11 +75,6 @@
      * However, it will be transparent to the {@link CameraControl} and {@link CameraInfo}
      * retrieved from {@link #getCameraControl()} and {@link #getCameraInfo()}.
      *
-     * <p> The CameraInternal are returned in the order of preference. The
-     * {@link CameraConfig} that is set via {@link #setExtendedConfig(CameraConfig)} can filter
-     * out specific instances of the CameraInternal. The remaining CameraInternal that comes
-     * first in this ordering will be used.
-     *
      * <p> The set of CameraInternal should be static for the lifetime of the Camera.
      *
      */
@@ -97,19 +91,6 @@
     CameraConfig getExtendedConfig();
 
     /**
-     * Set the extended config of the Camera.
-     *
-     * <p>This is used to apply additional configs that modifying the behavior of the camera and
-     * any attached {@link UseCase}. For example, it may configure the {@link ImageCapture} to use a
-     * {@link androidx.camera.core.impl.CaptureProcessor} in order to implement effects such as
-     * HDR or bokeh.
-     *
-     * @param cameraConfig if null then it will reset the camera to an empty config.
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    void setExtendedConfig(@Nullable CameraConfig cameraConfig);
-
-    /**
      * Checks whether the use cases combination is supported.
      *
      * @param useCases to be checked whether can be supported.
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceProcessor.java b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceProcessor.java
index a622897..f4be960 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceProcessor.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceProcessor.java
@@ -77,10 +77,15 @@
     /**
      * Invoked when CameraX requires an input {@link Surface} for reading original frames.
      *
+     * <p>CameraX requests {@link Surface}s when the upstream pipeline is reconfigured. For
+     * example, when {@link UseCase}s are bound to lifecycle.
+     *
      * <p>With OpenGL, the implementation should create a {@link Surface} backed by
      * {@link SurfaceTexture} with the size of {@link SurfaceRequest#getResolution()}, then
      * listen for the {@link SurfaceTexture#setOnFrameAvailableListener} to get the incoming
-     * frames.
+     * frames. The {@link Surface} should not be released until the callback provided in
+     * {@link SurfaceRequest#provideSurface} is invoked. CameraX may request new input
+     * {@link Surface} before releasing the existing one.
      *
      * <p>If the implementation encounters errors in creating the input {@link Surface}, it
      * should throw an {@link ProcessingException} to notify CameraX.
@@ -124,11 +129,16 @@
     /**
      * Invoked when CameraX provides output Surface(s) for drawing processed frames.
      *
+     * <p>CameraX provides the output {@link Surface}s when the downstream pipeline is
+     * reconfigured, for example, when {@link UseCase}s are bound or the preview viewfinder is
+     * reset.
+     *
      * <p>The provided {@link Surface}s are for drawing processed frames. The implementation must
      * get the {@link Surface} via {@link SurfaceOutput#getSurface} and provide a
      * {@link Consumer<SurfaceOutput.Event>} listening to the end-of-life event of the
      * {@link Surface}. Then, the implementation should call {@link SurfaceOutput#close()} after it
-     * stops drawing to the {@link Surface}.
+     * stops drawing to the {@link Surface}. CameraX may provide new output {@link Surface}
+     * before requesting to close the existing one.
      *
      * <p>If the implementation encounters an error and cannot consume the {@link Surface},
      * it should throw an {@link ProcessingException} to notify CameraX.
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraConfigs.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraConfigs.java
index 1eb8774e..e50a548 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraConfigs.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraConfigs.java
@@ -24,17 +24,17 @@
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public class CameraConfigs {
-    private static final CameraConfig EMPTY_CONFIG = new EmptyCameraConfig();
+    private static final CameraConfig DEFAULT_CAMERA_CONFIG = new DefaultCameraConfig();
 
     /**
      * Gets the empty config instance.
      */
     @NonNull
-    public static CameraConfig emptyConfig() {
-        return EMPTY_CONFIG;
+    public static CameraConfig defaultConfig() {
+        return DEFAULT_CAMERA_CONFIG;
     }
 
-    static final class EmptyCameraConfig implements CameraConfig {
+    static final class DefaultCameraConfig implements CameraConfig {
         private final Identifier mIdentifier = Identifier.create(new Object());
 
         @NonNull
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraInternal.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraInternal.java
index a80242d..4a1e3ce 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraInternal.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraInternal.java
@@ -224,13 +224,18 @@
         return new LinkedHashSet<>(Collections.singleton(this));
     }
 
+    /**
+     * Returns the current {@link CameraConfig}.
+     */
     @NonNull
     @Override
     default CameraConfig getExtendedConfig() {
-        return CameraConfigs.emptyConfig();
+        return CameraConfigs.defaultConfig();
     }
 
-    @Override
+    /**
+     * Sets the {@link CameraConfig} to configure the camera.
+     */
     default void setExtendedConfig(@Nullable CameraConfig cameraConfig) {
         // Ignore the config since CameraInternal won't use the config
     }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
index 877252a..0ed5456 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
@@ -138,7 +138,7 @@
     // Additional configs to apply onto the UseCases when added to this Camera
     @GuardedBy("mLock")
     @NonNull
-    private CameraConfig mCameraConfig = CameraConfigs.emptyConfig();
+    private final CameraConfig mCameraConfig;
 
     private final Object mLock = new Object();
 
@@ -166,16 +166,11 @@
     private final RestrictedCameraControl mAdapterCameraControl;
     @NonNull
     private final RestrictedCameraInfo mAdapterCameraInfo;
-
-
     /**
      * Create a new {@link CameraUseCaseAdapter} instance.
      *
      * @param cameras                    The set of cameras that are wrapped, with them in order
-     *                                   of preference. The actual camera used will be dependent
-     *                                   on configs set by
-     *                                   {@link #setExtendedConfig(CameraConfig)} which can
-     *                                   filter out specific camera instances
+     *                                   of preference.
      * @param cameraCoordinator          Camera coordinator that exposes concurrent camera mode.
      * @param cameraDeviceSurfaceManager A class that checks for whether a specific camera
      *                                   can support the set of Surface with set resolutions.
@@ -186,6 +181,29 @@
             @NonNull CameraCoordinator cameraCoordinator,
             @NonNull CameraDeviceSurfaceManager cameraDeviceSurfaceManager,
             @NonNull UseCaseConfigFactory useCaseConfigFactory) {
+        this(cameras, cameraCoordinator, cameraDeviceSurfaceManager, useCaseConfigFactory,
+                CameraConfigs.defaultConfig());
+    }
+
+    /**
+     * Create a new {@link CameraUseCaseAdapter} instance.
+     *
+     * @param cameras                    The set of cameras that are wrapped, with them in order
+     *                                   of preference.
+     * @param cameraCoordinator          Camera coordinator that exposes concurrent camera mode.
+     * @param cameraDeviceSurfaceManager A class that checks for whether a specific camera
+     *                                   can support the set of Surface with set resolutions.
+     * @param useCaseConfigFactory       UseCase config factory that exposes configuration for
+     *                                   each UseCase.
+     * @param cameraConfig               the CameraConfig to configure the {@link CameraInternal}
+     *                                   when attaching the uses cases of this adapter to the
+     *                                   camera.
+     */
+    public CameraUseCaseAdapter(@NonNull LinkedHashSet<CameraInternal> cameras,
+            @NonNull CameraCoordinator cameraCoordinator,
+            @NonNull CameraDeviceSurfaceManager cameraDeviceSurfaceManager,
+            @NonNull UseCaseConfigFactory useCaseConfigFactory,
+            @NonNull CameraConfig cameraConfig) {
         mCameraInternal = cameras.iterator().next();
         mCameraInternals = new LinkedHashSet<>(cameras);
         mId = new CameraId(mCameraInternals);
@@ -198,6 +216,20 @@
         mAdapterCameraInfo =
                 new RestrictedCameraInfo(mCameraInternal.getCameraInfoInternal(),
                         mAdapterCameraControl);
+
+        mCameraConfig = cameraConfig;
+        SessionProcessor sessionProcessor = mCameraConfig.getSessionProcessor(null);
+        if (sessionProcessor != null) {
+            @CameraOperation Set<Integer> supportedOps =
+                    sessionProcessor.getSupportedCameraOperations();
+            mAdapterCameraControl.enableRestrictedOperations(true, supportedOps);
+        } else {
+            mAdapterCameraControl.enableRestrictedOperations(false, null);
+        }
+        mAdapterCameraInfo.setPostviewSupported(
+                mCameraConfig.isPostviewSupported());
+        mAdapterCameraInfo.setCaptureProcessProgressSupported(
+                mCameraConfig.isCaptureProcessProgressSupported());
     }
 
     /**
@@ -249,6 +281,8 @@
      */
     public void addUseCases(@NonNull Collection<UseCase> appUseCasesToAdd) throws CameraException {
         synchronized (mLock) {
+            // Configure the CameraConfig when binding
+            mCameraInternal.setExtendedConfig(mCameraConfig);
             Set<UseCase> appUseCases = new LinkedHashSet<>(mAppUseCases);
             //TODO(b/266641900): must be LinkedHashSet otherwise ExistingActivityLifecycleTest
             // fails due to a camera-pipe integration bug.
@@ -590,6 +624,10 @@
     public void attachUseCases() {
         synchronized (mLock) {
             if (!mAttached) {
+                // Ensure the current opening camera has the right camera config.
+                if (!mCameraUseCases.isEmpty()) {
+                    mCameraInternal.setExtendedConfig(mCameraConfig);
+                }
                 mCameraInternal.attachUseCases(mCameraUseCases);
                 restoreInteropConfig();
 
@@ -988,8 +1026,8 @@
         return mCameraInternals;
     }
 
-    @NonNull
     @Override
+    @NonNull
     public CameraConfig getExtendedConfig() {
         synchronized (mLock) {
             return mCameraConfig;
@@ -997,37 +1035,6 @@
     }
 
     @Override
-    public void setExtendedConfig(@Nullable CameraConfig cameraConfig) {
-        synchronized (mLock) {
-            if (cameraConfig == null) {
-                cameraConfig = CameraConfigs.emptyConfig();
-            }
-
-            if (!mAppUseCases.isEmpty() && !mCameraConfig.getCompatibilityId().equals(
-                    cameraConfig.getCompatibilityId())) {
-                throw new IllegalStateException(
-                        "Need to unbind all use cases before binding with extension enabled");
-            }
-
-            mCameraConfig = cameraConfig;
-            SessionProcessor sessionProcessor = mCameraConfig.getSessionProcessor(null);
-            if (sessionProcessor != null) {
-                @CameraOperation Set<Integer> supportedOps =
-                        sessionProcessor.getSupportedCameraOperations();
-                mAdapterCameraControl.enableRestrictedOperations(true, supportedOps);
-            } else {
-                mAdapterCameraControl.enableRestrictedOperations(false, null);
-            }
-            mAdapterCameraInfo.setPostviewSupported(
-                    mCameraConfig.isPostviewSupported());
-            mAdapterCameraInfo.setCaptureProcessProgressSupported(
-                    mCameraConfig.isCaptureProcessProgressSupported());
-            //Configure the CameraInternal as well so that it can get SessionProcessor.
-            mCameraInternal.setExtendedConfig(mCameraConfig);
-        }
-    }
-
-    @Override
     public boolean isUseCasesCombinationSupported(boolean withStreamSharing,
             @NonNull UseCase... useCases) {
         Collection<UseCase> useCasesToVerify = Arrays.asList(useCases);
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/SupportedOutputSizesSorter.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/SupportedOutputSizesSorter.java
index 45efcbd..9498690 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/SupportedOutputSizesSorter.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/SupportedOutputSizesSorter.java
@@ -74,7 +74,7 @@
     private final Rational mFullFovRatio;
     private final SupportedOutputSizesSorterLegacy mSupportedOutputSizesSorterLegacy;
 
-    SupportedOutputSizesSorter(@NonNull CameraInfoInternal cameraInfoInternal,
+    public SupportedOutputSizesSorter(@NonNull CameraInfoInternal cameraInfoInternal,
             @Nullable Size activeArraySize) {
         mCameraInfoInternal = cameraInfoInternal;
         mSensorOrientation = mCameraInfoInternal.getSensorRotationDegrees();
@@ -120,7 +120,7 @@
      * will be sorted according to the legacy resolution API settings and logic.
      */
     @NonNull
-    List<Size> getSortedSupportedOutputSizes(@NonNull UseCaseConfig<?> useCaseConfig) {
+    public List<Size> getSortedSupportedOutputSizes(@NonNull UseCaseConfig<?> useCaseConfig) {
         ImageOutputConfig imageOutputConfig = (ImageOutputConfig) useCaseConfig;
         List<Size> customOrderedResolutions = imageOutputConfig.getCustomOrderedResolutions(null);
 
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/ResolutionsMerger.java b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/ResolutionsMerger.java
new file mode 100644
index 0000000..f97ebed
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/ResolutionsMerger.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core.streamsharing;
+
+
+import static androidx.camera.core.impl.ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE;
+import static androidx.camera.core.impl.ImageOutputConfig.OPTION_SUPPORTED_RESOLUTIONS;
+import static androidx.camera.core.impl.utils.AspectRatioUtil.ASPECT_RATIO_16_9;
+import static androidx.camera.core.impl.utils.AspectRatioUtil.ASPECT_RATIO_4_3;
+import static androidx.camera.core.impl.utils.AspectRatioUtil.hasMatchingAspectRatio;
+import static androidx.camera.core.impl.utils.TransformUtils.rectToSize;
+
+import static java.lang.Math.sqrt;
+
+import android.util.Pair;
+import android.util.Rational;
+import android.util.Size;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.VisibleForTesting;
+import androidx.camera.core.Logger;
+import androidx.camera.core.impl.CameraInfoInternal;
+import androidx.camera.core.impl.CameraInternal;
+import androidx.camera.core.impl.MutableConfig;
+import androidx.camera.core.impl.UseCaseConfig;
+import androidx.camera.core.impl.utils.CompareSizesByArea;
+import androidx.camera.core.internal.SupportedOutputSizesSorter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A class for calculating parent resolutions based on the children's configs.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class ResolutionsMerger {
+
+    private static final String TAG = "ResolutionsMerger";
+    // The width to height ratio that has same area when cropping into 4:3 and 16:9.
+    private static final double SAME_AREA_WIDTH_HEIGHT_RATIO = sqrt(4.0 / 3.0 * 16.0 / 9.0);
+
+    @NonNull
+    private final Rational mSensorAspectRatio;
+    @NonNull
+    private final Rational mFallbackAspectRatio;
+    @NonNull
+    private final Set<UseCaseConfig<?>> mChildrenConfigs;
+    @NonNull
+    private final SupportedOutputSizesSorter mSizeSorter;
+    @NonNull
+    private final List<Size> mCameraSupportedSizes;
+    @NonNull
+    private final Map<UseCaseConfig<?>, List<Size>> mChildSizesCache = new HashMap<>();
+
+    ResolutionsMerger(@NonNull CameraInternal cameraInternal,
+            @NonNull Set<UseCaseConfig<?>> childrenConfigs) {
+        this(rectToSize(cameraInternal.getCameraControlInternal().getSensorRect()),
+                cameraInternal.getCameraInfoInternal(), childrenConfigs);
+    }
+
+    private ResolutionsMerger(@NonNull Size sensorSize, @NonNull CameraInfoInternal cameraInfo,
+            @NonNull Set<UseCaseConfig<?>> childrenConfigs) {
+        this(sensorSize, childrenConfigs, new SupportedOutputSizesSorter(cameraInfo, sensorSize),
+                cameraInfo.getSupportedResolutions(INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE));
+    }
+
+    @VisibleForTesting
+    ResolutionsMerger(@NonNull Size sensorSize, @NonNull Set<UseCaseConfig<?>> childrenConfigs,
+            @NonNull SupportedOutputSizesSorter supportedOutputSizesSorter,
+            @NonNull List<Size> cameraSupportedResolutions) {
+        mSensorAspectRatio = getSensorAspectRatio(sensorSize);
+        mFallbackAspectRatio = getFallbackAspectRatio(mSensorAspectRatio);
+        mChildrenConfigs = childrenConfigs;
+        mSizeSorter = supportedOutputSizesSorter;
+        mCameraSupportedSizes = cameraSupportedResolutions;
+    }
+
+    /**
+     * Returns a list of {@link Surface} resolution sorted by priority.
+     *
+     * <p> This method calculates the resolution for the parent {@link StreamSharing} based on 1)
+     * the supported PRIV resolutions, 2) the sensor size and 3) the children's configs.
+     */
+    @NonNull
+    List<Size> getMergedResolutions(@NonNull MutableConfig parentConfig) {
+        List<Size> candidateSizes = mCameraSupportedSizes;
+
+        // Use parent config's supported resolutions when it is set (e.g. Extensions may have
+        // its limitations on resolutions).
+        List<Pair<Integer, Size[]>> parentSupportedSizesMap =
+                parentConfig.retrieveOption(OPTION_SUPPORTED_RESOLUTIONS, null);
+        if (parentSupportedSizesMap != null) {
+            candidateSizes = getSupportedPrivResolutions(parentSupportedSizesMap);
+        }
+
+        return mergeChildrenResolutions(candidateSizes);
+    }
+
+    /**
+     * Returns the preferred child size with considering parent size and child's configuration.
+     *
+     * <p>Returns the first size in the child's ordered size list that can be cropped from {@code
+     * parentSize} without upscaling it and causing double-cropping, or {@code parentSize} if no
+     * matching is found.
+     *
+     * <p>Notes that the input {@code childConfig} is expected to be one of the values that use to
+     * construct the {@link ResolutionsMerger}, if not an IllegalArgumentException will be thrown.
+     */
+    @NonNull
+    Size getPreferredChildSize(@NonNull Size parentSize, @NonNull UseCaseConfig<?> childConfig) {
+        boolean isParentCropped = !isSensorAspectRatio(parentSize);
+
+        List<Size> candidateChildSizes = getSortedChildSizes(childConfig);
+        for (Size childSize : candidateChildSizes) {
+            // Skip child sizes that need another cropping when parent is already cropped.
+            if (isParentCropped) {
+                boolean needAnotherCropping = !(isFallbackAspectRatio(parentSize)
+                        && isFallbackAspectRatio(childSize));
+                if (needAnotherCropping) {
+                    continue;
+                }
+            }
+
+            if (!hasUpscaling(childSize, parentSize)) {
+                return childSize;
+            }
+        }
+
+        return parentSize;
+    }
+
+    @NonNull
+    private List<Size> mergeChildrenResolutions(@NonNull List<Size> candidateParentResolutions) {
+        // The following sequence of parent resolution selection is used to prevent double-cropping
+        // from happening:
+        // 1. Add sensor aspect-ratio resolutions, which will not cause double-cropping when the
+        // child resolution is in any aspect-ratio. This is to provide parent resolution that can
+        // be accepted by children in general cases.
+        // 2. Add fallback aspect-ratio resolutions, which will not cause double-cropping only when
+        // the child resolution is in fallback aspect-ratio.
+        List<Size> result = new ArrayList<>();
+
+        // Add resolutions for sensor aspect-ratio.
+        if (needToAddSensorResolutions()) {
+            result.addAll(mergeChildrenResolutionsByAspectRatio(mSensorAspectRatio,
+                    candidateParentResolutions));
+        }
+
+        // Add resolutions for fallback aspect-ratio if needed.
+        if (needToAddFallbackResolutions()) {
+            result.addAll(mergeChildrenResolutionsByAspectRatio(mFallbackAspectRatio,
+                    candidateParentResolutions));
+        }
+
+        // TODO(b/315098647): When the resulting parent resolution list is empty, consider adding
+        //  resolutions that are neither 4:3 nor 16:9, but have a high overlap area (e.g. 80%)
+        //  compared to the sensor size, which do not cause severe reduction of FOV, to prevent
+        //  binding failures in some edge cases.
+
+        Logger.d(TAG, "Parent resolutions: " + result);
+
+        return result;
+    }
+
+    private List<Size> mergeChildrenResolutionsByAspectRatio(@NonNull Rational aspectRatio,
+            @NonNull List<Size> candidateParentResolutions) {
+        List<Size> candidates = filterResolutionsByAspectRatio(aspectRatio,
+                candidateParentResolutions);
+        sortInDescendingOrder(candidates);
+
+        // Filter resolutions that are too small and track resolutions that might be too large.
+        Set<Size> sizesTooLarge = new HashSet<>(candidates);
+        for (UseCaseConfig<?> childConfig : mChildrenConfigs) {
+            List<Size> childSizes = getSortedChildSizes(childConfig);
+            candidates = filterOutParentSizeThatIsTooSmall(childSizes, candidates);
+            sizesTooLarge.retainAll(getParentSizesThatAreTooLarge(childSizes, candidates));
+        }
+
+        // Filter out sizes that are too large.
+        List<Size> result = new ArrayList<>();
+        for (Size candidate : candidates) {
+            if (!sizesTooLarge.contains(candidate)) {
+                result.add(candidate);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Gets child sizes sorted by {@link SupportedOutputSizesSorter}.
+     *
+     * <p>Notes that the input {@code childConfig} is expected to be one of the values that use to
+     * construct the {@link ResolutionsMerger}, if not an IllegalArgumentException will be thrown.
+     *
+     * <p>When {@link SupportedOutputSizesSorter#getSortedSupportedOutputSizes(UseCaseConfig)}
+     * returns an empty list, which means no child required resolution is supported, an
+     * IllegalArgumentException will also be thrown.
+     */
+    @NonNull
+    private List<Size> getSortedChildSizes(@NonNull UseCaseConfig<?> childConfig) {
+        if (!mChildrenConfigs.contains(childConfig)) {
+            throw new IllegalArgumentException("Invalid child config: " + childConfig);
+        }
+
+        // Since getSortedSupportedOutputSizes() might be time consuming, use caching to improve
+        // the performance.
+        if (mChildSizesCache.containsKey(childConfig)) {
+            return Objects.requireNonNull(mChildSizesCache.get(childConfig));
+        }
+
+        List<Size> childSizes = mSizeSorter.getSortedSupportedOutputSizes(childConfig);
+        if (childSizes.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "No supported resolution for child config: " + childConfig);
+        }
+        mChildSizesCache.put(childConfig, childSizes);
+
+        return childSizes;
+    }
+
+    private boolean needToAddSensorResolutions() {
+        // Need to add sensor resolutions if any required resolution is not fallback aspect-ratio.
+        for (Size size : getChildrenRequiredResolutions()) {
+            if (!hasMatchingAspectRatio(size, mFallbackAspectRatio)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean needToAddFallbackResolutions() {
+        // Need to add fallback resolutions if any required resolution is fallback aspect-ratio.
+        for (Size size : getChildrenRequiredResolutions()) {
+            if (hasMatchingAspectRatio(size, mFallbackAspectRatio)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @NonNull
+    private Set<Size> getChildrenRequiredResolutions() {
+        Set<Size> result = new HashSet<>();
+        for (UseCaseConfig<?> childConfig : mChildrenConfigs) {
+            List<Size> childSizes = getSortedChildSizes(childConfig);
+            result.addAll(childSizes);
+        }
+
+        return result;
+    }
+
+    private boolean isSensorAspectRatio(@NonNull Size size) {
+        return hasMatchingAspectRatio(size, mSensorAspectRatio);
+    }
+
+    private boolean isFallbackAspectRatio(@NonNull Size size) {
+        return hasMatchingAspectRatio(size, mFallbackAspectRatio);
+    }
+
+    @NonNull
+    private static List<Size> getSupportedPrivResolutions(
+            @NonNull List<Pair<Integer, Size[]>> supportedResolutionsMap) {
+        for (Pair<Integer, Size[]> pair : supportedResolutionsMap) {
+            if (pair.first.equals(INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE)) {
+                return Arrays.asList(pair.second);
+            }
+        }
+
+        return new ArrayList<>();
+    }
+
+    /**
+     * Returns the aspect-ratio of 4:3 or 16:9 that is closer to the sensor size.
+     *
+     * <p>Parent resolutions with sensor aspect-ratio are considered to be non-cropped, so child
+     * resolution can have a different aspect-ratio than the parents without causing
+     * double-cropping.
+     */
+    @NonNull
+    private static Rational getSensorAspectRatio(@NonNull Size sensorSize) {
+        Rational result = findCloserAspectRatio(sensorSize);
+        Logger.d(TAG, "The closer aspect ratio to the sensor size (" + sensorSize + ") is "
+                + result + ".");
+
+        return result;
+    }
+
+    @NonNull
+    private static Rational findCloserAspectRatio(@NonNull Size size) {
+        double widthHeightRatio = size.getWidth() / (double) size.getHeight();
+
+        if (widthHeightRatio > SAME_AREA_WIDTH_HEIGHT_RATIO) {
+            return ASPECT_RATIO_16_9;
+        } else {
+            return ASPECT_RATIO_4_3;
+        }
+    }
+
+    /**
+     * Returns the aspect-ratio of 4:3 or 16:9 that is not the sensor aspect-ratio.
+     *
+     * <p>Parent resolutions with fallback aspect-ratio are considered to be cropped, so child
+     * resolution should not different to the parent or double-cropping will happen.
+     */
+    @NonNull
+    private static Rational getFallbackAspectRatio(@NonNull Rational sensorAspectRatio) {
+        if (sensorAspectRatio.equals(ASPECT_RATIO_4_3)) {
+            return ASPECT_RATIO_16_9;
+        } else if (sensorAspectRatio.equals(ASPECT_RATIO_16_9)) {
+            return ASPECT_RATIO_4_3;
+        } else {
+            throw new IllegalArgumentException("Invalid sensor aspect-ratio: " + sensorAspectRatio);
+        }
+    }
+
+    /**
+     * Sorts the input resolutions in descending order.
+     */
+    @VisibleForTesting
+    static void sortInDescendingOrder(@NonNull List<Size> resolutions) {
+        Collections.sort(resolutions, new CompareSizesByArea(true));
+    }
+
+    /**
+     * Returns a list of resolution that all resolutions are with the input aspect-ratio.
+     *
+     * <p>The order of the {@code resolutionsToFilter} will be preserved in the resulting list.
+     */
+    @VisibleForTesting
+    @NonNull
+    static List<Size> filterResolutionsByAspectRatio(@NonNull Rational aspectRatio,
+            @NonNull List<Size> resolutionsToFilter) {
+        List<Size> result = new ArrayList<>();
+        for (Size resolution : resolutionsToFilter) {
+            if (hasMatchingAspectRatio(resolution, aspectRatio)) {
+                result.add(resolution);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Filters out the parent size that is too small with consider target children sizes.
+     *
+     * <p>A size is too small if it cannot find any child size that can be cropped out without
+     * upscaling.
+     *
+     * <p>The order of the {@code sortedParentSizes} will be preserved in the resulting list.
+     *
+     * <p>Assuming {@code sortedParentSizes} is sorted in descending order and all sizes have same
+     * aspect-ratio.
+     */
+    @VisibleForTesting
+    @NonNull
+    static List<Size> filterOutParentSizeThatIsTooSmall(
+            @NonNull Collection<Size> childSizes, @NonNull List<Size> sortedParentSizes) {
+        int n = sortedParentSizes.size();
+
+        // Find the smallest parent size that can be cropped to at least one child size without
+        // upscaling by using binary search.
+        int lo = 0;
+        int hi = n - 1;
+        while (lo < hi) {
+            int mid = lo + (hi - lo + 1) / 2;
+            Size parentSize = sortedParentSizes.get(mid);
+            if (isAnyChildSizeCanBeCroppedOutWithoutUpscalingParent(childSizes, parentSize)) {
+                lo = mid;
+            } else {
+                hi = mid - 1;
+            }
+        }
+
+        // Add all parent sizes that can be cropped to at least one child size.
+        List<Size> result = new ArrayList<>();
+        for (int i = 0; i <= lo; i++) {
+            result.add(sortedParentSizes.get(i));
+        }
+
+        return result;
+    }
+
+    private static boolean isAnyChildSizeCanBeCroppedOutWithoutUpscalingParent(
+            @NonNull Collection<Size> childSizes, @NonNull Size parentSize) {
+        for (Size childSize : childSizes) {
+            if (!hasUpscaling(childSize, parentSize)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns resolutions that are too large with consider target children sizes.
+     *
+     * <p>A size is too large if there is another size smaller than that size and all children
+     * sizes can be cropped from that other size without upscaling.
+     *
+     * <p>The order of the {@code sortedParentSizes} will be preserved in the resulting list.
+     *
+     * <p>Assuming {@code sortedParentSizes} is sorted in descending order and all sizes have same
+     * aspect-ratio.
+     */
+    @VisibleForTesting
+    @NonNull
+    static List<Size> getParentSizesThatAreTooLarge(@NonNull Collection<Size> childSizes,
+            @NonNull List<Size> sortedParentSizes) {
+        int n = sortedParentSizes.size();
+
+        // Find the smallest parent size that can be cropped to all child sizes without upscaling
+        // by using binary search.
+        int lo = 0;
+        int hi = n - 1;
+        while (lo < hi) {
+            int mid = lo + (hi - lo + 1) / 2;
+            Size parentSize = sortedParentSizes.get(mid);
+            if (isAllChildSizesCanBeCroppedOutWithoutUpscalingParent(childSizes, parentSize)) {
+                lo = mid;
+            } else {
+                hi = mid - 1;
+            }
+        }
+
+        // Add all parent sizes that can be cropped to all child sizes, except the smallest one.
+        List<Size> result = new ArrayList<>();
+        for (int i = 0; i < lo; i++) {
+            result.add(sortedParentSizes.get(i));
+        }
+
+        return result;
+    }
+
+    private static boolean isAllChildSizesCanBeCroppedOutWithoutUpscalingParent(
+            @NonNull Collection<Size> childSizes, @NonNull Size parentSize) {
+        for (Size childSize : childSizes) {
+            if (hasUpscaling(childSize, parentSize)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Whether the parent size needs upscaling to fill the child size.
+     */
+    @VisibleForTesting
+    static boolean hasUpscaling(@NonNull Size childSize, @NonNull Size parentSize) {
+        // Upscaling is needed if child size is larger than the parent.
+        return childSize.getHeight() > parentSize.getHeight()
+                || childSize.getWidth() > parentSize.getWidth();
+    }
+}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt
index 265a359..f04f6e7 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt
@@ -33,13 +33,10 @@
 import androidx.camera.core.CameraEffect.VIDEO_CAPTURE
 import androidx.camera.core.MirrorMode.MIRROR_MODE_OFF
 import androidx.camera.core.MirrorMode.MIRROR_MODE_ON_FRONT_ONLY
-import androidx.camera.core.impl.CameraConfig
 import androidx.camera.core.impl.CameraFactory
 import androidx.camera.core.impl.CaptureConfig
-import androidx.camera.core.impl.Identifier
 import androidx.camera.core.impl.ImageCaptureConfig
 import androidx.camera.core.impl.MutableOptionsBundle
-import androidx.camera.core.impl.OptionsBundle
 import androidx.camera.core.impl.SessionConfig
 import androidx.camera.core.impl.SessionProcessor
 import androidx.camera.core.impl.StreamSpec
@@ -554,18 +551,16 @@
 
         cameraUseCaseAdapter = CameraUtil.createCameraUseCaseAdapter(
             ApplicationProvider.getApplicationContext(),
-            CameraSelector.DEFAULT_BACK_CAMERA
-        )
-
-        val cameraConfig = FakeCameraConfig(
-            sessionProcessor = FakeSessionProcessor(
-                postviewSupportedSizes = mapOf(
-                    ImageFormat.JPEG to
-                        listOf(Size(1920, 1080), Size(640, 480)))
+            CameraSelector.DEFAULT_BACK_CAMERA,
+            FakeCameraConfig(
+                sessionProcessor = FakeSessionProcessor(
+                    postviewSupportedSizes = mapOf(
+                        ImageFormat.JPEG to
+                            listOf(Size(1920, 1080), Size(640, 480))
+                    )
+                )
             )
         )
-
-        cameraUseCaseAdapter.setExtendedConfig(cameraConfig)
         cameraUseCaseAdapter.addUseCases(listOf(imageCapture))
         assertThat(imageCapture.imagePipeline!!.postviewSize).isEqualTo(Size(1920, 1080))
     }
@@ -584,18 +579,16 @@
 
         cameraUseCaseAdapter = CameraUtil.createCameraUseCaseAdapter(
             ApplicationProvider.getApplicationContext(),
-            CameraSelector.DEFAULT_BACK_CAMERA
-        )
-
-        val cameraConfig = FakeCameraConfig(
-            sessionProcessor = FakeSessionProcessor(
-                postviewSupportedSizes = mapOf(
-                    ImageFormat.JPEG to
-                        listOf(Size(4000, 3000), Size(1920, 1080)))
+            CameraSelector.DEFAULT_BACK_CAMERA,
+            FakeCameraConfig(
+                sessionProcessor = FakeSessionProcessor(
+                    postviewSupportedSizes = mapOf(
+                        ImageFormat.JPEG to
+                            listOf(Size(4000, 3000), Size(1920, 1080)))
+                )
             )
         )
 
-        cameraUseCaseAdapter.setExtendedConfig(cameraConfig)
         cameraUseCaseAdapter.addUseCases(listOf(imageCapture))
         assertThat(imageCapture.imagePipeline!!.postviewSize).isEqualTo(Size(1920, 1080))
     }
@@ -612,17 +605,15 @@
 
         cameraUseCaseAdapter = CameraUtil.createCameraUseCaseAdapter(
             ApplicationProvider.getApplicationContext(),
-            CameraSelector.DEFAULT_BACK_CAMERA
-        )
-
-        val cameraConfig = FakeCameraConfig(
-            sessionProcessor = FakeSessionProcessor(
-                postviewSupportedSizes = mapOf(
-                    ImageFormat.JPEG to listOf(Size(1920, 1080)))
+            CameraSelector.DEFAULT_BACK_CAMERA,
+            FakeCameraConfig(
+                sessionProcessor = FakeSessionProcessor(
+                    postviewSupportedSizes = mapOf(
+                        ImageFormat.JPEG to listOf(Size(1920, 1080)))
+                )
             )
         )
 
-        cameraUseCaseAdapter.setExtendedConfig(cameraConfig)
         // the CameraException will be converted to IllegalArgumentException in camera-lifecycle.
         assertThrows(CameraUseCaseAdapter.CameraException::class.java) {
             cameraUseCaseAdapter.addUseCases(listOf(imageCapture))
@@ -645,34 +636,14 @@
             imageReaderProxyProvider,
         )
 
+        val cameraConfig = FakeCameraConfig(sessionProcessor = sessionProcessor)
         cameraUseCaseAdapter = CameraUtil.createCameraUseCaseAdapter(
             ApplicationProvider.getApplicationContext(),
-            cameraSelector
+            cameraSelector,
+            cameraConfig
         )
 
         cameraUseCaseAdapter.setViewPort(viewPort)
-        if (sessionProcessor != null) {
-            cameraUseCaseAdapter.setExtendedConfig(object : CameraConfig {
-                override fun getConfig(): androidx.camera.core.impl.Config {
-                    return OptionsBundle.emptyBundle()
-                }
-
-                override fun getSessionProcessor(
-                    valueIfMissing: SessionProcessor?
-                ): SessionProcessor {
-                    return sessionProcessor
-                }
-
-                override fun getSessionProcessor(): SessionProcessor {
-                    return sessionProcessor
-                }
-
-                override fun getCompatibilityId(): Identifier {
-                    return Identifier.create(Any())
-                }
-            })
-        }
-
         cameraUseCaseAdapter.addUseCases(Collections.singleton<UseCase>(imageCapture))
         return imageCapture
     }
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/impl/ExtendedCameraConfigProviderStoreTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/impl/ExtendedCameraConfigProviderStoreTest.kt
index 27291ae..07418da 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/impl/ExtendedCameraConfigProviderStoreTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/impl/ExtendedCameraConfigProviderStoreTest.kt
@@ -33,7 +33,7 @@
     public fun canRetrieveStoredCameraConfigProvider() {
         val id = Object()
         val cameraConfigProvider =
-            CameraConfigProvider { _, _ -> CameraConfigs.emptyConfig() }
+            CameraConfigProvider { _, _ -> CameraConfigs.defaultConfig() }
 
         ExtendedCameraConfigProviderStore.addConfig(id, cameraConfigProvider)
 
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
index 7b3c239..acda8c7 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt
@@ -60,6 +60,7 @@
 import androidx.camera.testing.fakes.FakeCamera
 import androidx.camera.testing.fakes.FakeCameraControl
 import androidx.camera.testing.fakes.FakeCameraInfoInternal
+import androidx.camera.testing.impl.fakes.FakeCameraConfig
 import androidx.camera.testing.impl.fakes.FakeCameraCoordinator
 import androidx.camera.testing.impl.fakes.FakeCameraDeviceSurfaceManager
 import androidx.camera.testing.impl.fakes.FakeSessionProcessor
@@ -201,6 +202,68 @@
         assertThat(fakeCamera.attachedUseCases).isEmpty()
     }
 
+    @Test
+    fun addUseCases_cameraConfigIsConfigured() {
+        // Arrange: Prepare two sets of CameraConfig and CameraUseCaseAdapter.
+        val cameraConfig = FakeCameraConfig()
+        val adapter = CameraUseCaseAdapter(
+            fakeCameraSet,
+            cameraCoordinator,
+            fakeCameraDeviceSurfaceManager,
+            useCaseConfigFactory,
+            cameraConfig
+        )
+        val cameraInternal = fakeCameraSet.iterator().next();
+
+        // Act: Add use cases.
+        adapter.addUseCases(setOf(preview, video, image))
+
+        // Assert:  CameraConfig is configured to the underlying CameraInternal.
+        assertThat(cameraInternal.extendedConfig).isSameInstanceAs(cameraConfig)
+    }
+
+    @Test
+    fun attachUseCases_cameraConfigIsConfigured() {
+        // Arrange: Prepare two sets of CameraConfig and CameraUseCaseAdapter.
+        val cameraConfig1 = FakeCameraConfig()
+        val adapter1 = CameraUseCaseAdapter(
+            fakeCameraSet,
+            cameraCoordinator,
+            fakeCameraDeviceSurfaceManager,
+            useCaseConfigFactory,
+            cameraConfig1
+        )
+        val cameraConfig2 = FakeCameraConfig()
+        val adapter2 = CameraUseCaseAdapter(
+            fakeCameraSet,
+            cameraCoordinator,
+            fakeCameraDeviceSurfaceManager,
+            useCaseConfigFactory,
+            cameraConfig2,
+            )
+        val cameraInternal = fakeCameraSet.iterator().next();
+        val preview2 = Preview.Builder().build()
+        val video2 = createFakeVideoCaptureUseCase()
+        val image2 = ImageCapture.Builder().build()
+
+        // Act:  bind adapter1 and attach to camera, bind adapter 2 and attach to camera, and then
+        // switch back to adapter1 to attach to camera (without unbinding).
+        adapter1.detachUseCases()
+        adapter1.addUseCases(setOf(preview, video, image))
+        adapter1.attachUseCases()
+
+        adapter1.detachUseCases()
+        adapter2.detachUseCases()
+        adapter2.addUseCases(setOf(preview2, video2, image2))
+        adapter2.attachUseCases()
+
+        adapter2.detachUseCases()
+        adapter1.attachUseCases()
+
+        // Assert: CameraConfig1 is configured because adapter1 is active (attached to camera)
+        assertThat(cameraInternal.extendedConfig).isSameInstanceAs(cameraConfig1)
+    }
+
     @RequiresApi(33) // 10-bit HDR only supported on API 33+
     @Test
     fun canUseHdrWithoutExtensions() {
@@ -218,7 +281,14 @@
     fun useHDRWithExtensions_throwsException() {
         // Arrange: enable extensions.
         val extensionsConfig = createCoexistingRequiredRuleCameraConfig(FakeSessionProcessor())
-        adapter.setExtendedConfig(extensionsConfig)
+
+        val adapter = CameraUseCaseAdapter(
+            fakeCameraSet,
+            cameraCoordinator,
+            fakeCameraDeviceSurfaceManager,
+            useCaseConfigFactory,
+            extensionsConfig,
+        )
         // Act: add UseCase that uses HDR.
         val hdrUseCase = FakeUseCaseConfig.Builder().setDynamicRange(HDR_UNSPECIFIED_10_BIT).build()
         adapter.addUseCases(setOf(hdrUseCase))
@@ -372,7 +442,13 @@
     fun extensionEnabledAndVideoCaptureExisted_streamSharingOn() {
         // Arrange: enable extensions.
         val extensionsConfig = createCoexistingRequiredRuleCameraConfig(FakeSessionProcessor())
-        adapter.setExtendedConfig(extensionsConfig)
+        val adapter = CameraUseCaseAdapter(
+            fakeCameraSet,
+            cameraCoordinator,
+            fakeCameraDeviceSurfaceManager,
+            useCaseConfigFactory,
+            extensionsConfig,
+        )
         // Act: add UseCases that require StreamSharing.
         adapter.addUseCases(setOf(preview, video, image))
         // Assert: StreamSharing exists and bound.
@@ -389,7 +465,13 @@
     fun extensionEnabledAndOnlyVideoCaptureAttached_streamSharingOn() {
         // Arrange: enable extensions.
         val extensionsConfig = createCoexistingRequiredRuleCameraConfig(FakeSessionProcessor())
-        adapter.setExtendedConfig(extensionsConfig)
+        val adapter = CameraUseCaseAdapter(
+            fakeCameraSet,
+            cameraCoordinator,
+            fakeCameraDeviceSurfaceManager,
+            useCaseConfigFactory,
+            extensionsConfig,
+        )
         // Act: add UseCases that require StreamSharing.
         adapter.addUseCases(setOf(video))
         // Assert: StreamSharing exists and bound.
@@ -704,80 +786,14 @@
     }
 
     @Test
-    fun canSetExtendedCameraConfig_whenNoUseCase() {
-        val cameraUseCaseAdapter = CameraUseCaseAdapter(
-            fakeCameraSet,
-            cameraCoordinator,
-            fakeCameraDeviceSurfaceManager,
-            useCaseConfigFactory
-        )
-        cameraUseCaseAdapter.setExtendedConfig(FakeCameraConfig())
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun canNotSetExtendedCameraConfig_whenUseCaseHasExisted() {
-        val cameraUseCaseAdapter = CameraUseCaseAdapter(
-            fakeCameraSet,
-            cameraCoordinator,
-            fakeCameraDeviceSurfaceManager,
-            useCaseConfigFactory
-        )
-
-        // Adds use case first
-        cameraUseCaseAdapter.addUseCases(listOf(FakeUseCase()))
-
-        // Sets extended config after a use case is added
-        cameraUseCaseAdapter.setExtendedConfig(FakeCameraConfig())
-    }
-
-    @Test
-    fun canSetSameExtendedCameraConfig_whenUseCaseHasExisted() {
-        val cameraUseCaseAdapter = CameraUseCaseAdapter(
-            fakeCameraSet,
-            cameraCoordinator,
-            fakeCameraDeviceSurfaceManager,
-            useCaseConfigFactory
-        )
-        val cameraConfig: CameraConfig = FakeCameraConfig()
-        cameraUseCaseAdapter.setExtendedConfig(cameraConfig)
-        cameraUseCaseAdapter.addUseCases(listOf(FakeUseCase()))
-
-        // Sets extended config with the same camera config
-        cameraUseCaseAdapter.setExtendedConfig(cameraConfig)
-    }
-
-    @Test
-    fun canSwitchExtendedCameraConfig_afterUnbindUseCases() {
-        val cameraUseCaseAdapter = CameraUseCaseAdapter(
-            fakeCameraSet,
-            cameraCoordinator,
-            fakeCameraDeviceSurfaceManager,
-            useCaseConfigFactory
-        )
-        val cameraConfig1: CameraConfig = FakeCameraConfig()
-        cameraUseCaseAdapter.setExtendedConfig(cameraConfig1)
-
-        // Binds use case
-        val fakeUseCase = FakeUseCase()
-        cameraUseCaseAdapter.addUseCases(listOf(fakeUseCase))
-
-        // Unbinds use case
-        cameraUseCaseAdapter.removeUseCases(listOf(fakeUseCase))
-
-        // Sets extended config with different camera config
-        val cameraConfig2: CameraConfig = FakeCameraConfig()
-        cameraUseCaseAdapter.setExtendedConfig(cameraConfig2)
-    }
-
-    @Test
     fun noExtraUseCase_whenBindEmptyUseCaseList() {
         val cameraUseCaseAdapter = CameraUseCaseAdapter(
             fakeCameraSet,
             cameraCoordinator,
             fakeCameraDeviceSurfaceManager,
-            useCaseConfigFactory
+            useCaseConfigFactory,
+            createCoexistingRequiredRuleCameraConfig()
         )
-        cameraUseCaseAdapter.setExtendedConfig(createCoexistingRequiredRuleCameraConfig())
         cameraUseCaseAdapter.addUseCases(emptyList())
         val useCases = cameraUseCaseAdapter.useCases
         assertThat(useCases.size).isEqualTo(0)
@@ -789,9 +805,9 @@
             fakeCameraSet,
             cameraCoordinator,
             fakeCameraDeviceSurfaceManager,
-            useCaseConfigFactory
+            useCaseConfigFactory,
+            createCoexistingRequiredRuleCameraConfig()
         )
-        cameraUseCaseAdapter.setExtendedConfig(createCoexistingRequiredRuleCameraConfig())
         val preview = Preview.Builder().build()
 
         // Adds a Preview only
@@ -807,16 +823,16 @@
             fakeCameraSet,
             cameraCoordinator,
             fakeCameraDeviceSurfaceManager,
-            useCaseConfigFactory
+            useCaseConfigFactory,
+            createCoexistingRequiredRuleCameraConfig()
         )
-        cameraUseCaseAdapter.setExtendedConfig(createCoexistingRequiredRuleCameraConfig())
         val preview = Preview.Builder().build()
 
         // Adds a Preview only
         cameraUseCaseAdapter.addUseCases(listOf(preview))
 
         // Checks whether an extra ImageCapture is added.
-        assertThat(containsImageCapture(cameraUseCaseAdapter.cameraUseCases))
+        assertThat(containsImageCapture(cameraUseCaseAdapter.cameraUseCases)).isTrue()
         val imageCapture = ImageCapture.Builder().build()
 
         // Adds an ImageCapture
@@ -832,9 +848,9 @@
             fakeCameraSet,
             cameraCoordinator,
             fakeCameraDeviceSurfaceManager,
-            useCaseConfigFactory
+            useCaseConfigFactory,
+            createCoexistingRequiredRuleCameraConfig()
         )
-        cameraUseCaseAdapter.setExtendedConfig(createCoexistingRequiredRuleCameraConfig())
         val useCases = mutableListOf<UseCase>()
         val preview = Preview.Builder().build()
         val imageCapture = ImageCapture.Builder().build()
@@ -860,9 +876,9 @@
             fakeCameraSet,
             cameraCoordinator,
             fakeCameraDeviceSurfaceManager,
-            useCaseConfigFactory
+            useCaseConfigFactory,
+            createCoexistingRequiredRuleCameraConfig()
         )
-        cameraUseCaseAdapter.setExtendedConfig(createCoexistingRequiredRuleCameraConfig())
         val imageCapture = ImageCapture.Builder().build()
 
         // Adds an ImageCapture only
@@ -878,16 +894,16 @@
             fakeCameraSet,
             cameraCoordinator,
             fakeCameraDeviceSurfaceManager,
-            useCaseConfigFactory
+            useCaseConfigFactory,
+            createCoexistingRequiredRuleCameraConfig()
         )
-        cameraUseCaseAdapter.setExtendedConfig(createCoexistingRequiredRuleCameraConfig())
         val imageCapture = ImageCapture.Builder().build()
 
         // Adds a ImageCapture only
         cameraUseCaseAdapter.addUseCases(listOf(imageCapture))
 
         // Checks whether an extra Preview is added.
-        assertThat(containsPreview(cameraUseCaseAdapter.cameraUseCases))
+        assertThat(containsPreview(cameraUseCaseAdapter.cameraUseCases)).isTrue()
         val preview = Preview.Builder().build()
 
         // Adds an Preview
@@ -902,9 +918,9 @@
             fakeCameraSet,
             cameraCoordinator,
             fakeCameraDeviceSurfaceManager,
-            useCaseConfigFactory
+            useCaseConfigFactory,
+            createCoexistingRequiredRuleCameraConfig()
         )
-        cameraUseCaseAdapter.setExtendedConfig(createCoexistingRequiredRuleCameraConfig())
         val useCases = mutableListOf<UseCase>()
         val preview = Preview.Builder().build()
         val imageCapture = ImageCapture.Builder().build()
@@ -930,9 +946,9 @@
             fakeCameraSet,
             cameraCoordinator,
             fakeCameraDeviceSurfaceManager,
-            useCaseConfigFactory
+            useCaseConfigFactory,
+            createCoexistingRequiredRuleCameraConfig()
         )
-        cameraUseCaseAdapter.setExtendedConfig(createCoexistingRequiredRuleCameraConfig())
         val useCases = mutableListOf<UseCase>()
         val preview = Preview.Builder().build()
         val imageCapture = ImageCapture.Builder().build()
@@ -1050,18 +1066,19 @@
     private fun createAdapterWithSupportedCameraOperations(
         @RestrictedCameraControl.CameraOperation supportedOps: Set<Int>
     ): CameraUseCaseAdapter {
-        val cameraUseCaseAdapter = CameraUseCaseAdapter(
-            fakeCameraSet,
-            cameraCoordinator,
-            fakeCameraDeviceSurfaceManager,
-            useCaseConfigFactory
-        )
-
         val fakeSessionProcessor = FakeSessionProcessor()
         // no camera operations are supported.
         fakeSessionProcessor.restrictedCameraOperations = supportedOps
         val cameraConfig: CameraConfig = FakeCameraConfig(fakeSessionProcessor)
-        cameraUseCaseAdapter.setExtendedConfig(cameraConfig)
+
+        val cameraUseCaseAdapter = CameraUseCaseAdapter(
+            fakeCameraSet,
+            cameraCoordinator,
+            fakeCameraDeviceSurfaceManager,
+            useCaseConfigFactory,
+            cameraConfig
+        )
+
         return cameraUseCaseAdapter
     }
 
@@ -1374,39 +1391,33 @@
     @Test
     fun cameraInfo_postviewSupported(): Unit = runBlocking {
         // 1. Arrange
+        val cameraConfig: CameraConfig = FakeCameraConfig(postviewSupported = true)
         val cameraUseCaseAdapter = CameraUseCaseAdapter(
             fakeCameraSet,
             cameraCoordinator,
             fakeCameraDeviceSurfaceManager,
-            useCaseConfigFactory
+            useCaseConfigFactory,
+            cameraConfig
         )
         val cameraInfoInternal = cameraUseCaseAdapter.cameraInfo as CameraInfoInternal
-        assertThat(cameraInfoInternal.isPostviewSupported).isFalse()
-        // 2. Act
-        val cameraConfig: CameraConfig = FakeCameraConfig(postviewSupported = true)
-        cameraUseCaseAdapter.setExtendedConfig(cameraConfig)
-
-        // 3. Assert
+        // 2. Act && Assert
         assertThat(cameraInfoInternal.isPostviewSupported).isTrue()
     }
 
     @Test
     fun cameraInfo_captureProcessProgressSupported(): Unit = runBlocking {
         // 1. Arrange
+        val cameraConfig: CameraConfig = FakeCameraConfig(captureProcessProgressSupported = true)
         val cameraUseCaseAdapter = CameraUseCaseAdapter(
             fakeCameraSet,
             cameraCoordinator,
             fakeCameraDeviceSurfaceManager,
-            useCaseConfigFactory
+            useCaseConfigFactory,
+            cameraConfig
         )
         val cameraInfoInternal = cameraUseCaseAdapter.cameraInfo as CameraInfoInternal
-        assertThat(cameraInfoInternal.isCaptureProcessProgressSupported).isFalse()
 
-        // 2. Act
-        val cameraConfig: CameraConfig = FakeCameraConfig(captureProcessProgressSupported = true)
-        cameraUseCaseAdapter.setExtendedConfig(cameraConfig)
-
-        // 3. Assert
+        // 2. Act && Assert
         assertThat(cameraInfoInternal.isCaptureProcessProgressSupported).isTrue()
     }
 
@@ -1468,42 +1479,4 @@
         }
         return false
     }
-
-    private class FakeCameraConfig(
-        val sessionProcessor: FakeSessionProcessor? = null,
-        val postviewSupported: Boolean = false,
-        val captureProcessProgressSupported: Boolean = false
-    ) : CameraConfig {
-        private val mUseCaseConfigFactory =
-            UseCaseConfigFactory { _, _ -> null }
-        private val mIdentifier = Identifier.create(Any())
-
-        override fun getUseCaseConfigFactory(): UseCaseConfigFactory {
-            return mUseCaseConfigFactory
-        }
-
-        override fun isPostviewSupported(): Boolean {
-            return postviewSupported
-        }
-
-        override fun isCaptureProcessProgressSupported(): Boolean {
-            return captureProcessProgressSupported
-        }
-
-        override fun getCompatibilityId(): Identifier {
-            return mIdentifier
-        }
-
-        override fun getConfig(): Config {
-            return OptionsBundle.emptyBundle()
-        }
-
-        override fun getSessionProcessor(valueIfMissing: SessionProcessor?): SessionProcessor? {
-            return sessionProcessor ?: valueIfMissing
-        }
-
-        override fun getSessionProcessor(): SessionProcessor {
-            return sessionProcessor!!
-        }
-    }
 }
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/ResolutionsMergerTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/ResolutionsMergerTest.kt
new file mode 100644
index 0000000..6caf41a
--- /dev/null
+++ b/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/ResolutionsMergerTest.kt
@@ -0,0 +1,348 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core.streamsharing
+
+import android.os.Build
+import android.util.Size
+import androidx.camera.core.impl.MutableOptionsBundle
+import androidx.camera.core.impl.UseCaseConfig
+import androidx.camera.core.impl.utils.AspectRatioUtil.ASPECT_RATIO_16_9
+import androidx.camera.core.impl.utils.AspectRatioUtil.ASPECT_RATIO_4_3
+import androidx.camera.core.internal.SupportedOutputSizesSorter
+import androidx.camera.core.streamsharing.ResolutionsMerger.filterOutParentSizeThatIsTooSmall
+import androidx.camera.core.streamsharing.ResolutionsMerger.filterResolutionsByAspectRatio
+import androidx.camera.core.streamsharing.ResolutionsMerger.getParentSizesThatAreTooLarge
+import androidx.camera.core.streamsharing.ResolutionsMerger.hasUpscaling
+import androidx.camera.testing.fakes.FakeCameraInfoInternal
+import androidx.camera.testing.impl.fakes.FakeUseCaseConfig
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+
+/**
+ * Unit tests for [ResolutionsMerger].
+ */
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class ResolutionsMergerTest {
+
+    @Test(expected = IllegalArgumentException::class)
+    fun getMergedResolutions_whenNoSupportedChildSize_throwsException() {
+        // Arrange.
+        val sensorSize = SIZE_3264_2448 // 4:3
+        val config1 = createUseCaseConfig()
+        val config2 = createUseCaseConfig()
+        val childConfigs = setOf(config1, config2)
+        val candidateChildSizes1 = listOf(SIZE_1920_1080, SIZE_1280_720) // 16:9
+        val candidateChildSizes2 = emptyList<Size>() // no supported size
+        val sorter = FakeSupportedOutputSizesSorter(
+            mapOf(
+                config1 to candidateChildSizes1,
+                config2 to candidateChildSizes2
+            )
+        )
+        val merger = ResolutionsMerger(sensorSize, childConfigs, sorter, CAMERA_SUPPORTED_SIZES)
+
+        // Act & Assert.
+        val parentConfig = MutableOptionsBundle.create()
+        merger.getMergedResolutions(parentConfig)
+    }
+
+    @Test
+    fun getMergedResolutions_whenChildRequiresSensorAndNonSensorAspectRatio_canReturnCorrectly() {
+        // Arrange.
+        val sensorSize = SIZE_3264_2448 // 4:3
+        val config1 = createUseCaseConfig()
+        val config2 = createUseCaseConfig()
+        val childConfigs = setOf(config1, config2)
+        val candidateChildSizes1 = listOf(SIZE_1920_1080, SIZE_1280_720) // 16:9
+        val candidateChildSizes2 = listOf(SIZE_1280_960, SIZE_960_720, SIZE_640_480) // 4:3
+        val sorter = FakeSupportedOutputSizesSorter(
+            mapOf(
+                config1 to candidateChildSizes1,
+                config2 to candidateChildSizes2
+            )
+        )
+        val merger = ResolutionsMerger(sensorSize, childConfigs, sorter, CAMERA_SUPPORTED_SIZES)
+
+        // Act & Assert, should returns a list that concatenates 4:3 resolutions before 16:9
+        // resolutions and removes resolutions that are too large (no need for multiple resolutions
+        // that can be cropped to all child sizes) and too small (causing upscaling).
+        val parentConfig = MutableOptionsBundle.create()
+        assertThat(merger.getMergedResolutions(parentConfig)).containsExactly(
+            SIZE_1920_1440, SIZE_1280_960, SIZE_1920_1080, SIZE_1280_720
+        ).inOrder()
+    }
+
+    @Test
+    fun getMergedResolutions_whenChildRequiresOnlySensorAspectRatio_canReturnCorrectly() {
+        // Arrange.
+        val sensorSize = SIZE_3264_2448 // 4:3
+        val config1 = createUseCaseConfig()
+        val config2 = createUseCaseConfig()
+        val childConfigs = setOf(config1, config2)
+        val candidateChildSizes1 = listOf(SIZE_2560_1920, SIZE_1920_1440) // 4:3
+        val candidateChildSizes2 = listOf(SIZE_1280_960, SIZE_960_720) // 4:3
+        val sorter = FakeSupportedOutputSizesSorter(
+            mapOf(
+                config1 to candidateChildSizes1,
+                config2 to candidateChildSizes2
+            )
+        )
+        val merger = ResolutionsMerger(sensorSize, childConfigs, sorter, CAMERA_SUPPORTED_SIZES)
+
+        // Act & Assert, should returns a list of 4:3 resolutions and removes resolutions that are
+        // too large and too small.
+        val parentConfig = MutableOptionsBundle.create()
+        assertThat(merger.getMergedResolutions(parentConfig)).containsExactly(
+            SIZE_2560_1920, SIZE_1920_1440
+        ).inOrder()
+    }
+
+    @Test
+    fun getMergedResolutions_whenChildRequiresOnlyNonSensorAspectRatio_canReturnCorrectly() {
+        // Arrange.
+        val sensorSize = SIZE_3264_2448 // 4:3
+        val config1 = createUseCaseConfig()
+        val config2 = createUseCaseConfig()
+        val childConfigs = setOf(config1, config2)
+        val candidateChildSizes1 = listOf(SIZE_2560_1440, SIZE_1280_720) // 16:9
+        val candidateChildSizes2 = listOf(SIZE_1920_1080, SIZE_960_540) // 16:9
+        val sorter = FakeSupportedOutputSizesSorter(
+            mapOf(
+                config1 to candidateChildSizes1,
+                config2 to candidateChildSizes2
+            )
+        )
+        val merger = ResolutionsMerger(sensorSize, childConfigs, sorter, CAMERA_SUPPORTED_SIZES)
+
+        // Act & Assert, should returns a list of 16:9 resolutions and removes resolutions that are
+        // too large and too small.
+        val parentConfig = MutableOptionsBundle.create()
+        assertThat(merger.getMergedResolutions(parentConfig)).containsExactly(
+            SIZE_2560_1440, SIZE_1920_1080, SIZE_1280_720
+        ).inOrder()
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun getPreferredChildSize_withConfigNotPassedToConstructor_throwsException() {
+        // Arrange.
+        val config = createUseCaseConfig()
+        val sorter = FakeSupportedOutputSizesSorter(mapOf(config to SIZES_16_9))
+        val merger = ResolutionsMerger(SENSOR_SIZE, setOf(config), sorter, CAMERA_SUPPORTED_SIZES)
+
+        // Act.
+        val useCaseConfigNotPassedToConstructor = createUseCaseConfig()
+        merger.getPreferredChildSize(SIZE_1920_1440, useCaseConfigNotPassedToConstructor)
+    }
+
+    @Test
+    fun getPreferredChildSize_whenParentSizeIsSensorAspectRatio_canReturnCorrectly() {
+        // Arrange.
+        val config = createUseCaseConfig()
+        val candidateChildSizes = listOf(SIZE_2560_1440, SIZE_1920_1080, SIZE_960_540) // 16:9
+        val sorter = FakeSupportedOutputSizesSorter(mapOf(config to candidateChildSizes))
+        val merger = ResolutionsMerger(SENSOR_SIZE, setOf(config), sorter, CAMERA_SUPPORTED_SIZES)
+
+        // Act & Assert, should returns the first child size that do not need upscale.
+        assertThat(merger.getPreferredChildSize(SIZE_1920_1440, config)).isEqualTo(SIZE_1920_1080)
+        assertThat(merger.getPreferredChildSize(SIZE_1280_960, config)).isEqualTo(SIZE_960_540)
+
+        // Act & Assert, should returns parent size when no matching.
+        assertThat(merger.getPreferredChildSize(SIZE_640_480, config)).isEqualTo(SIZE_640_480)
+    }
+
+    @Test
+    fun getPreferredChildSize_whenParentSizeIsNotSensorAspectRatio_canReturnCorrectly() {
+        // Arrange.
+        val config = createUseCaseConfig()
+        val candidateChildSizes = listOf(
+            // 4:3
+            SIZE_2560_1920,
+            SIZE_1920_1440,
+            SIZE_1280_960,
+            SIZE_960_720,
+            // 16:9
+            SIZE_1920_1080,
+            SIZE_960_540
+        )
+        val sorter = FakeSupportedOutputSizesSorter(mapOf(config to candidateChildSizes))
+        val merger = ResolutionsMerger(SENSOR_SIZE, setOf(config), sorter, CAMERA_SUPPORTED_SIZES)
+
+        // Act & Assert, should returns the first child size that do not need upscale and cause
+        // double-cropping.
+        assertThat(merger.getPreferredChildSize(SIZE_2560_1440, config)).isEqualTo(SIZE_1920_1080)
+        assertThat(merger.getPreferredChildSize(SIZE_1280_720, config)).isEqualTo(SIZE_960_540)
+
+        // Act & Assert, should returns parent size when no matching.
+        assertThat(merger.getPreferredChildSize(SIZE_192_108, config)).isEqualTo(SIZE_192_108)
+    }
+
+    @Test
+    fun filterResolutionsByAspectRatio_canFilter_4_3() {
+        val sizes = SIZES_4_3 + SIZES_16_9 + SIZES_OTHER_ASPECT_RATIO
+        assertThat(filterResolutionsByAspectRatio(ASPECT_RATIO_4_3, sizes)).containsExactly(
+            *SIZES_4_3.toTypedArray()
+        ).inOrder()
+    }
+
+    @Test
+    fun filterResolutionsByAspectRatio_canFilter_16_9() {
+        val sizes = SIZES_4_3 + SIZES_16_9 + SIZES_OTHER_ASPECT_RATIO
+        assertThat(filterResolutionsByAspectRatio(ASPECT_RATIO_16_9, sizes)).containsExactly(
+            *SIZES_16_9.toTypedArray()
+        ).inOrder()
+    }
+
+    @Test
+    fun filterOutParentSizeThatIsTooSmall_canFilterOutSmallSizes() {
+        val parentSizes = listOf(
+            SIZE_3264_2448,
+            SIZE_2560_1920,
+            SIZE_1920_1440,
+            SIZE_1280_960,
+            SIZE_960_720,
+            SIZE_640_480,
+            SIZE_320_240
+        )
+        val childSizes = setOf(SIZE_1920_1080, SIZE_1280_720, SIZE_960_540)
+        assertThat(filterOutParentSizeThatIsTooSmall(childSizes, parentSizes)).containsExactly(
+            SIZE_3264_2448,
+            SIZE_2560_1920,
+            SIZE_1920_1440,
+            SIZE_1280_960,
+            SIZE_960_720,
+        ).inOrder()
+    }
+
+    @Test
+    fun getParentSizesThatAreTooLarge_canReturnLargeSizes() {
+        val parentSizes = listOf(
+            SIZE_3264_2448,
+            SIZE_2560_1920,
+            SIZE_1920_1440,
+            SIZE_1280_960,
+            SIZE_960_720,
+            SIZE_640_480,
+            SIZE_320_240
+        )
+        val childSizes = setOf(SIZE_1920_1080, SIZE_1280_720, SIZE_960_540)
+        assertThat(getParentSizesThatAreTooLarge(childSizes, parentSizes)).containsExactly(
+            SIZE_3264_2448,
+            SIZE_2560_1920
+        ).inOrder()
+    }
+
+    @Test
+    fun hasUpscaling_return_false_whenTwoSizesAreEqualed() {
+        assertThat(hasUpscaling(SIZE_1280_960, SIZE_1280_960)).isFalse()
+        assertThat(hasUpscaling(SIZE_1920_1080, SIZE_1920_1080)).isFalse()
+    }
+
+    @Test
+    fun hasUpscaling_return_false_whenChildSizeIsSmaller() {
+        assertThat(hasUpscaling(SIZE_1280_960, SIZE_1920_1440)).isFalse()
+        assertThat(hasUpscaling(SIZE_1280_720, SIZE_1920_1080)).isFalse()
+    }
+
+    @Test
+    fun hasUpscaling_return_true_whenChildSizeIsLarger() {
+        assertThat(hasUpscaling(SIZE_1920_1440, SIZE_1280_960)).isTrue()
+        assertThat(hasUpscaling(SIZE_1920_1080, SIZE_1280_720)).isTrue()
+    }
+
+    @Test
+    fun hasUpscaling_return_true_whenChildSizeIsLargerOnWidth() {
+        assertThat(hasUpscaling(SIZE_1440_720, SIZE_1280_960)).isTrue()
+        assertThat(hasUpscaling(SIZE_1440_720, SIZE_1280_720)).isTrue()
+    }
+
+    @Test
+    fun hasUpscaling_return_true_whenChildSizeIsLargerOnHeight() {
+        assertThat(hasUpscaling(SIZE_800_800, SIZE_1280_720)).isTrue()
+        assertThat(hasUpscaling(SIZE_720_720, SIZE_960_540)).isTrue()
+    }
+
+    private fun createUseCaseConfig(): UseCaseConfig<*> {
+        return FakeUseCaseConfig.Builder().useCaseConfig
+    }
+
+    /**
+     * A fake implementation of [SupportedOutputSizesSorter] for testing.
+     */
+    private class FakeSupportedOutputSizesSorter(
+        private val supportedOutputSizes: Map<UseCaseConfig<*>, List<Size>>
+    ) : SupportedOutputSizesSorter(FakeCameraInfoInternal(), null) {
+        override fun getSortedSupportedOutputSizes(useCaseConfig: UseCaseConfig<*>): List<Size> {
+            return supportedOutputSizes[useCaseConfig]!!
+        }
+    }
+
+    companion object {
+        // 4:3 resolutions.
+        private val SIZE_3264_2448 = Size(3264, 2448)
+        private val SIZE_2560_1920 = Size(2560, 1920)
+        private val SIZE_1920_1440 = Size(1920, 1440)
+        private val SIZE_1280_960 = Size(1280, 960)
+        private val SIZE_960_720 = Size(960, 720)
+        private val SIZE_640_480 = Size(640, 480)
+        private val SIZE_320_240 = Size(320, 240)
+        private val SIZES_4_3 = listOf(
+            SIZE_3264_2448,
+            SIZE_2560_1920,
+            SIZE_1920_1440,
+            SIZE_1280_960,
+            SIZE_960_720,
+            SIZE_640_480,
+            SIZE_320_240
+        )
+        // 16:9 resolutions.
+        private val SIZE_3840_2160 = Size(3840, 2160)
+        private val SIZE_2560_1440 = Size(2560, 1440)
+        private val SIZE_1920_1080 = Size(1920, 1080)
+        private val SIZE_1280_720 = Size(1280, 720)
+        private val SIZE_960_540 = Size(960, 540)
+        private val SIZE_192_108 = Size(192, 108)
+        private val SIZES_16_9 = listOf(
+            SIZE_3840_2160,
+            SIZE_2560_1440,
+            SIZE_1920_1080,
+            SIZE_1280_720,
+            SIZE_960_540,
+            SIZE_192_108
+        )
+        // Other aspect-ratio resolutions.
+        private val SIZE_1440_720 = Size(1440, 720)
+        private val SIZE_800_800 = Size(800, 800)
+        private val SIZE_720_720 = Size(720, 720)
+        private val SIZE_500_400 = Size(500, 400)
+        private val SIZE_176_144 = Size(176, 144)
+        private val SIZES_OTHER_ASPECT_RATIO = listOf(
+            SIZE_1440_720,
+            SIZE_800_800,
+            SIZE_720_720,
+            SIZE_500_400,
+            SIZE_176_144
+        )
+        private val CAMERA_SUPPORTED_SIZES = SIZES_4_3 + SIZES_16_9 + SIZES_OTHER_ASPECT_RATIO
+        private val SENSOR_SIZE = SIZE_3264_2448 // 4:3
+    }
+}
diff --git a/camera/camera-effects/src/main/java/androidx/camera/effects/opengl/GlContext.java b/camera/camera-effects/src/main/java/androidx/camera/effects/opengl/GlContext.java
index 56919d0..504e18a 100644
--- a/camera/camera-effects/src/main/java/androidx/camera/effects/opengl/GlContext.java
+++ b/camera/camera-effects/src/main/java/androidx/camera/effects/opengl/GlContext.java
@@ -273,7 +273,8 @@
         if (!EGL14.eglMakeCurrent(mEglDisplay, eglSurface.getEglSurface(),
                 eglSurface.getEglSurface(),
                 mEglContext)) {
-            throw new IllegalStateException("eglMakeCurrent failed");
+            throw new IllegalStateException(
+                    "eglMakeCurrent failed. GL Error: " + EGL14.eglGetError());
         }
 
         mCurrentSurface = eglSurface;
diff --git a/camera/camera-extensions/build.gradle b/camera/camera-extensions/build.gradle
index 3ae5439..fbe84e3 100644
--- a/camera/camera-extensions/build.gradle
+++ b/camera/camera-extensions/build.gradle
@@ -30,7 +30,6 @@
     implementation("androidx.core:core:1.0.0")
     implementation("androidx.concurrent:concurrent-futures:1.0.0")
     implementation(libs.autoValueAnnotations)
-    implementation(project(':camera:camera-camera2-pipe-integration'))
     annotationProcessor(libs.autoValue)
 
     compileOnly(project(":camera:camera-extensions-stub"))
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageCaptureTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageCaptureTest.kt
index 96616ad..0818c348 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageCaptureTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/ImageCaptureTest.kt
@@ -39,6 +39,7 @@
 import androidx.camera.testing.impl.ExifUtil
 import androidx.camera.testing.impl.SurfaceTextureProvider
 import androidx.camera.testing.impl.SurfaceTextureProvider.SurfaceTextureCallback
+import androidx.camera.testing.impl.WakelockEmptyActivityRule
 import androidx.camera.testing.impl.fakes.FakeLifecycleOwner
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.filters.LargeTest
@@ -81,6 +82,9 @@
     val temporaryFolder =
         TemporaryFolder(ApplicationProvider.getApplicationContext<Context>().cacheDir)
 
+    @get:Rule
+    val wakelockEmptyActivityRule = WakelockEmptyActivityRule()
+
     private val context = ApplicationProvider.getApplicationContext<Context>()
 
     private lateinit var cameraProvider: ProcessCameraProvider
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionCameraFilter.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionCameraFilter.java
index a859edb..0da3b2b 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionCameraFilter.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionCameraFilter.java
@@ -19,12 +19,16 @@
 import android.hardware.camera2.CameraCharacteristics;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.OptIn;
 import androidx.annotation.RequiresApi;
+import androidx.camera.camera2.interop.Camera2CameraInfo;
+import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
 import androidx.camera.core.CameraFilter;
 import androidx.camera.core.CameraInfo;
+import androidx.camera.core.impl.CameraInfoInternal;
 import androidx.camera.core.impl.Identifier;
-import androidx.camera.extensions.internal.Camera2CameraInfoWrapper;
 import androidx.camera.extensions.internal.VendorExtender;
+import androidx.core.util.Preconditions;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -50,15 +54,18 @@
         return mId;
     }
 
+    @OptIn(markerClass = ExperimentalCamera2Interop.class)
     @NonNull
     @Override
     public List<CameraInfo> filter(@NonNull List<CameraInfo> cameraInfos) {
         List<CameraInfo> result = new ArrayList<>();
         for (CameraInfo cameraInfo : cameraInfos) {
-            String cameraId = Camera2CameraInfoWrapper.from(cameraInfo).getCameraId();
+            Preconditions.checkArgument(cameraInfo instanceof CameraInfoInternal,
+                    "The camera info doesn't contain internal implementation.");
+            String cameraId = Camera2CameraInfo.from(cameraInfo).getCameraId();
 
             Map<String, CameraCharacteristics> cameraCharacteristicsMap =
-                    Camera2CameraInfoWrapper.from(cameraInfo).getCameraCharacteristicsMap();
+                    Camera2CameraInfo.from(cameraInfo).getCameraCharacteristicsMap();
 
             if (mVendorExtender
                     .isExtensionAvailable(cameraId, cameraCharacteristicsMap)) {
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdvancedVendorExtender.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdvancedVendorExtender.java
index 11975ad..c70750a 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdvancedVendorExtender.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdvancedVendorExtender.java
@@ -26,8 +26,11 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.OptIn;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.VisibleForTesting;
+import androidx.camera.camera2.interop.Camera2CameraInfo;
+import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
 import androidx.camera.core.CameraInfo;
 import androidx.camera.core.Logger;
 import androidx.camera.core.impl.SessionProcessor;
@@ -94,12 +97,13 @@
         mMode = ExtensionMode.NONE;
     }
 
+    @OptIn(markerClass = ExperimentalCamera2Interop.class)
     @Override
     public void init(@NonNull CameraInfo cameraInfo) {
-        mCameraId = Camera2CameraInfoWrapper.from(cameraInfo).getCameraId();
+        mCameraId = Camera2CameraInfo.from(cameraInfo).getCameraId();
 
         Map<String, CameraCharacteristics> cameraCharacteristicsMap =
-                Camera2CameraInfoWrapper.from(cameraInfo).getCameraCharacteristicsMap();
+                Camera2CameraInfo.from(cameraInfo).getCameraCharacteristicsMap();
 
         mAdvancedExtenderImpl.init(mCameraId, cameraCharacteristicsMap);
     }
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java
index e190a59..b170459 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BasicVendorExtender.java
@@ -29,8 +29,11 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.OptIn;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.VisibleForTesting;
+import androidx.camera.camera2.interop.Camera2CameraInfo;
+import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
 import androidx.camera.core.CameraInfo;
 import androidx.camera.core.Logger;
 import androidx.camera.core.impl.ImageFormatConstants;
@@ -91,7 +94,6 @@
             CaptureRequest.FLASH_MODE,
             CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION
     ));
-
     static {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
             sBaseSupportedKeys.add(CaptureRequest.CONTROL_ZOOM_RATIO);
@@ -156,6 +158,7 @@
                 && mImageCaptureExtenderImpl.isExtensionAvailable(cameraId, cameraCharacteristics);
     }
 
+    @OptIn(markerClass = ExperimentalCamera2Interop.class)
     @Override
     public void init(@NonNull CameraInfo cameraInfo) {
         mCameraInfo = cameraInfo;
@@ -164,9 +167,9 @@
             return;
         }
 
-        mCameraId = Camera2CameraInfoWrapper.from(cameraInfo).getCameraId();
+        mCameraId = Camera2CameraInfo.from(cameraInfo).getCameraId();
         mCameraCharacteristics =
-                Camera2CameraInfoWrapper.extractCameraCharacteristics(cameraInfo);
+                Camera2CameraInfo.extractCameraCharacteristics(cameraInfo);
         mPreviewExtenderImpl.init(mCameraId, mCameraCharacteristics);
         mImageCaptureExtenderImpl.init(mCameraId, mCameraCharacteristics);
 
@@ -189,8 +192,9 @@
         return null;
     }
 
+    @OptIn(markerClass = ExperimentalCamera2Interop.class)
     private Size[] getOutputSizes(int imageFormat) {
-        StreamConfigurationMap map = Camera2CameraInfoWrapper.from(mCameraInfo)
+        StreamConfigurationMap map = Camera2CameraInfo.from(mCameraInfo)
                 .getCameraCharacteristic(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
 
         return map.getOutputSizes(imageFormat);
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/Camera2CameraInfoWrapper.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/Camera2CameraInfoWrapper.java
deleted file mode 100644
index 48d3935..0000000
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/Camera2CameraInfoWrapper.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.extensions.internal;
-
-import android.hardware.camera2.CameraCharacteristics;
-import android.os.Build;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.OptIn;
-import androidx.annotation.RequiresApi;
-import androidx.camera.camera2.internal.Camera2CameraInfoImpl;
-import androidx.camera.camera2.pipe.integration.adapter.CameraInfoAdapter;
-import androidx.camera.core.CameraInfo;
-import androidx.camera.core.impl.CameraInfoInternal;
-
-import java.util.Map;
-
-
-@OptIn(markerClass = {androidx.camera.camera2.interop.ExperimentalCamera2Interop.class,
-        androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop.class})
-@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
-public final class Camera2CameraInfoWrapper {
-    private androidx.camera.camera2.interop.Camera2CameraInfo mCamera2CameraInfo;
-    private androidx.camera.camera2.pipe.integration.interop.Camera2CameraInfo
-            mCameraPipeCameraInfo;
-
-    Camera2CameraInfoWrapper(@NonNull Camera2CameraInfoImpl camera2CameraInfo) {
-        mCamera2CameraInfo =
-                androidx.camera.camera2.interop.Camera2CameraInfo.from(camera2CameraInfo);
-    }
-
-    Camera2CameraInfoWrapper(@NonNull CameraInfoAdapter cameraInfoAdapter) {
-        mCameraPipeCameraInfo =
-                androidx.camera.camera2.pipe.integration.interop.Camera2CameraInfo.from(
-                        cameraInfoAdapter);
-    }
-
-    @NonNull
-    public static Camera2CameraInfoWrapper from(@NonNull CameraInfo cameraInfo) {
-        CameraInfoInternal cameraInfoInternal =
-                ((CameraInfoInternal) cameraInfo).getImplementation();
-        if (cameraInfoInternal instanceof Camera2CameraInfoImpl) {
-            return new Camera2CameraInfoWrapper((Camera2CameraInfoImpl) cameraInfoInternal);
-        } else if (cameraInfoInternal instanceof CameraInfoAdapter) {
-            return new Camera2CameraInfoWrapper((CameraInfoAdapter) cameraInfoInternal);
-        } else {
-            throw new IllegalArgumentException("Not a Camera2 implementation!");
-        }
-    }
-
-    @NonNull
-    public String getCameraId() {
-        if (mCamera2CameraInfo != null) {
-            return mCamera2CameraInfo.getCameraId();
-        } else {
-            return mCameraPipeCameraInfo.getCameraId();
-        }
-    }
-
-    @NonNull
-    public <T> T getCameraCharacteristic(@NonNull CameraCharacteristics.Key<T> key) {
-        if (mCamera2CameraInfo != null) {
-            return mCamera2CameraInfo.getCameraCharacteristic(key);
-        } else {
-            return mCameraPipeCameraInfo.getCameraCharacteristic(key);
-        }
-    }
-
-    @NonNull
-    public static CameraCharacteristics extractCameraCharacteristics(
-            @NonNull CameraInfo cameraInfo) {
-        CameraInfoInternal cameraInfoInternal =
-                ((CameraInfoInternal) cameraInfo).getImplementation();
-        if (cameraInfoInternal instanceof Camera2CameraInfoImpl) {
-            return androidx.camera.camera2.interop.Camera2CameraInfo.extractCameraCharacteristics(
-                    cameraInfo);
-        } else if (cameraInfoInternal instanceof CameraInfoAdapter) {
-            return androidx.camera.camera2.pipe.integration.interop.Camera2CameraInfo
-                    .extractCameraCharacteristics(cameraInfo);
-        } else {
-            throw new IllegalArgumentException("Not a Camera2 implementation!");
-        }
-    }
-
-    @NonNull
-    public Map<String, CameraCharacteristics> getCameraCharacteristicsMap() {
-        if (mCamera2CameraInfo != null) {
-            return mCamera2CameraInfo.getCameraCharacteristicsMap();
-        } else {
-            return mCameraPipeCameraInfo.getCameraCharacteristicsMap();
-        }
-    }
-}
diff --git a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/LifecycleCameraRepositoryTest.java b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/LifecycleCameraRepositoryTest.java
index 63ae7a7..56ab0c1 100644
--- a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/LifecycleCameraRepositoryTest.java
+++ b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/LifecycleCameraRepositoryTest.java
@@ -23,9 +23,12 @@
 import static java.util.Collections.emptyList;
 
 import androidx.camera.core.concurrent.CameraCoordinator;
+import androidx.camera.core.impl.CameraConfig;
+import androidx.camera.core.impl.CameraConfigs;
 import androidx.camera.core.impl.CameraInternal;
 import androidx.camera.core.internal.CameraUseCaseAdapter;
 import androidx.camera.testing.fakes.FakeCamera;
+import androidx.camera.testing.impl.fakes.FakeCameraConfig;
 import androidx.camera.testing.impl.fakes.FakeCameraCoordinator;
 import androidx.camera.testing.impl.fakes.FakeCameraDeviceSurfaceManager;
 import androidx.camera.testing.impl.fakes.FakeLifecycleOwner;
@@ -106,6 +109,19 @@
     }
 
     @Test
+    public void differentLifecycleCamerasAreCreated_forDifferentCameraConfig() {
+        LifecycleCamera firstLifecycleCamera = mRepository.createLifecycleCamera(
+                mLifecycle, mCameraUseCaseAdapter);
+
+        // Creates LifecycleCamera with different camera set
+        LifecycleCamera secondLifecycleCamera =
+                mRepository.createLifecycleCamera(mLifecycle,
+                        createCameraUseCaseAdapterWithNewCameraConfig());
+
+        assertThat(firstLifecycleCamera).isNotEqualTo(secondLifecycleCamera);
+    }
+
+    @Test
     public void lifecycleCameraIsNotActive_createWithNoUseCasesAfterLifecycleStarted() {
         mLifecycle.start();
         LifecycleCamera lifecycleCamera = mRepository.createLifecycleCamera(mLifecycle,
@@ -431,17 +447,43 @@
         LifecycleCamera lifecycleCamera = mRepository.createLifecycleCamera(
                 mLifecycle, mCameraUseCaseAdapter);
         CameraUseCaseAdapter.CameraId cameraId = CameraUseCaseAdapter.generateCameraId(mCameraSet);
-        LifecycleCamera retrieved = mRepository.getLifecycleCamera(mLifecycle, cameraId);
+        LifecycleCamera retrieved = mRepository.getLifecycleCamera(mLifecycle, cameraId,
+                mCameraUseCaseAdapter.getExtendedConfig());
 
         assertThat(lifecycleCamera).isSameInstanceAs(retrieved);
     }
 
     @Test
+    public void getLifecycleCameraWithDifferentCameraConfig_returnDifferentInstance() {
+        LifecycleCamera lifecycleCamera1 = mRepository.createLifecycleCamera(mLifecycle,
+                mCameraUseCaseAdapter);
+
+        CameraUseCaseAdapter newCameraUseCaseAdapter =
+                createCameraUseCaseAdapterWithNewCameraConfig();
+        LifecycleCamera lifecycleCamera2 = mRepository.createLifecycleCamera(mLifecycle,
+                newCameraUseCaseAdapter);
+
+        LifecycleCamera retrieved1 = mRepository.getLifecycleCamera(mLifecycle,
+                mCameraUseCaseAdapter.getCameraId(),
+                mCameraUseCaseAdapter.getExtendedConfig());
+
+        LifecycleCamera retrieved2 = mRepository.getLifecycleCamera(mLifecycle,
+                newCameraUseCaseAdapter.getCameraId(),
+                newCameraUseCaseAdapter.getExtendedConfig());
+
+        assertThat(lifecycleCamera1).isSameInstanceAs(retrieved1);
+        assertThat(lifecycleCamera2).isSameInstanceAs(retrieved2);
+        assertThat(retrieved1).isNotSameInstanceAs(retrieved2);
+    }
+
+    @Test
     public void keys() {
         LifecycleCameraRepository.Key key0 = LifecycleCameraRepository.Key.create(mLifecycle,
-                mCameraUseCaseAdapter.getCameraId());
+                mCameraUseCaseAdapter.getCameraId(),
+                CameraConfigs.defaultConfig().getCompatibilityId());
         LifecycleCameraRepository.Key key1 = LifecycleCameraRepository.Key.create(mLifecycle,
-                CameraUseCaseAdapter.generateCameraId(mCameraSet));
+                CameraUseCaseAdapter.generateCameraId(mCameraSet),
+                CameraConfigs.defaultConfig().getCompatibilityId());
 
         Map<LifecycleCameraRepository.Key, LifecycleOwner> map = new HashMap<>();
         map.put(key0, mLifecycle);
@@ -586,4 +628,13 @@
                 new FakeCameraDeviceSurfaceManager(),
                 new FakeUseCaseConfigFactory());
     }
+
+    private CameraUseCaseAdapter createCameraUseCaseAdapterWithNewCameraConfig() {
+        CameraConfig cameraConfig = new FakeCameraConfig();
+        return new CameraUseCaseAdapter(mCameraSet,
+                mCameraCoordinator,
+                new FakeCameraDeviceSurfaceManager(),
+                new FakeUseCaseConfigFactory(),
+                cameraConfig);
+    }
 }
diff --git a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
index 0939e5f..ec280ea2 100644
--- a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
+++ b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt
@@ -23,6 +23,8 @@
 import android.content.pm.PackageManager.FEATURE_CAMERA_CONCURRENT
 import androidx.annotation.OptIn
 import androidx.annotation.RequiresApi
+import androidx.camera.core.CameraFilter
+import androidx.camera.core.CameraInfo
 import androidx.camera.core.CameraSelector
 import androidx.camera.core.CameraSelector.LENS_FACING_BACK
 import androidx.camera.core.CameraSelector.LENS_FACING_FRONT
@@ -31,7 +33,13 @@
 import androidx.camera.core.Preview
 import androidx.camera.core.UseCaseGroup
 import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_UNSPECIFIED
+import androidx.camera.core.impl.CameraConfig
 import androidx.camera.core.impl.CameraFactory
+import androidx.camera.core.impl.Config
+import androidx.camera.core.impl.ExtendedCameraConfigProviderStore
+import androidx.camera.core.impl.Identifier
+import androidx.camera.core.impl.MutableOptionsBundle
+import androidx.camera.core.impl.SessionProcessor
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
 import androidx.camera.testing.fakes.FakeAppConfig
 import androidx.camera.testing.fakes.FakeCamera
@@ -40,6 +48,7 @@
 import androidx.camera.testing.impl.fakes.FakeCameraDeviceSurfaceManager
 import androidx.camera.testing.impl.fakes.FakeCameraFactory
 import androidx.camera.testing.impl.fakes.FakeLifecycleOwner
+import androidx.camera.testing.impl.fakes.FakeSessionProcessor
 import androidx.camera.testing.impl.fakes.FakeSurfaceEffect
 import androidx.camera.testing.impl.fakes.FakeSurfaceProcessor
 import androidx.camera.testing.impl.fakes.FakeUseCaseConfigFactory
@@ -846,6 +855,81 @@
         }
     }
 
+    @Test
+    @RequiresApi(23)
+    fun bindWithExtensions_doesNotImpactPreviousCamera(): Unit = runBlocking(Dispatchers.Main) {
+        // 1. Arrange.
+        val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
+        val cameraSelectorWithExtensions = getCameraSelectorWithLimitedCapabilities(
+            cameraSelector,
+            emptySet() // All capabilities are not supported.
+        )
+        provider = ProcessCameraProvider.getInstance(context).await()
+        val useCase = Preview.Builder().build()
+
+        // 2. Act: bind with and then without Extensions.
+        // bind with regular cameraSelector to get the regular camera (with empty use cases)
+        val camera = provider.bindToLifecycle(lifecycleOwner0, cameraSelector)
+        // bind with extensions cameraSelector to get the restricted version of camera.
+        val cameraWithExtensions = provider.bindToLifecycle(lifecycleOwner0,
+            cameraSelectorWithExtensions, useCase)
+
+        // 3. Assert: ensure we can different instances of Camera and one does not affect the other.
+        assertThat(camera).isNotSameInstanceAs(cameraWithExtensions)
+
+        // only the Extensions CameraControl does not support the zoom.
+        camera.cameraControl.setZoomRatio(1.0f).await()
+        assertThrows<IllegalStateException> {
+            cameraWithExtensions.cameraControl.setZoomRatio(1.0f).await()
+        }
+
+        // only the Extensions CameraInfo does not support the zoom.
+        assertThat(camera.cameraInfo.zoomState.value!!.maxZoomRatio).isGreaterThan(1.0f)
+        assertThat(cameraWithExtensions.cameraInfo.zoomState.value!!.maxZoomRatio).isEqualTo(1.0f)
+    }
+
+    @RequiresApi(23)
+    private fun getCameraSelectorWithLimitedCapabilities(
+        cameraSelector: CameraSelector,
+        supportedCapabilities: Set<Int>
+    ): CameraSelector {
+        val identifier = Identifier.create("idStr")
+        val sessionProcessor = FakeSessionProcessor()
+        sessionProcessor.restrictedCameraOperations = supportedCapabilities
+        ExtendedCameraConfigProviderStore.addConfig(identifier) { _, _ ->
+            object : CameraConfig {
+                override fun getConfig(): Config {
+                    return MutableOptionsBundle.create()
+                }
+
+                override fun getCompatibilityId(): Identifier {
+                    return identifier
+                }
+
+                override fun getSessionProcessor(
+                    valueIfMissing: SessionProcessor?
+                ) = sessionProcessor
+
+                override fun getSessionProcessor() = sessionProcessor
+            }
+        }
+
+        val builder = CameraSelector.Builder.fromSelector(cameraSelector)
+        builder.addCameraFilter(object : CameraFilter {
+            override fun filter(cameraInfos: MutableList<CameraInfo>): MutableList<CameraInfo> {
+                val newCameraInfos = mutableListOf<CameraInfo>()
+                newCameraInfos.addAll(cameraInfos)
+                return newCameraInfos
+            }
+
+            override fun getIdentifier(): Identifier {
+                return identifier
+            }
+        })
+
+        return builder.build()
+    }
+
     private fun createConcurrentCameraAppConfig(): CameraXConfig {
         val combination0 = mapOf(
             "0" to CameraSelector.Builder().requireLensFacing(LENS_FACING_BACK).build(),
diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCamera.java b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCamera.java
index 056e784..8df240b 100644
--- a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCamera.java
+++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCamera.java
@@ -20,7 +20,6 @@
 
 import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.camera.core.Camera;
 import androidx.camera.core.CameraControl;
@@ -276,11 +275,6 @@
     }
 
     @Override
-    public void setExtendedConfig(@Nullable CameraConfig cameraConfig)  {
-        mCameraUseCaseAdapter.setExtendedConfig(cameraConfig);
-    }
-
-    @Override
     public boolean isUseCasesCombinationSupported(boolean withStreamSharing,
             @NonNull UseCase... useCases) {
         return mCameraUseCaseAdapter.isUseCasesCombinationSupported(withStreamSharing, useCases);
diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraRepository.java b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraRepository.java
index 3b8fae9..3023fbc 100644
--- a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraRepository.java
+++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraRepository.java
@@ -24,7 +24,9 @@
 import androidx.camera.core.UseCase;
 import androidx.camera.core.ViewPort;
 import androidx.camera.core.concurrent.CameraCoordinator;
+import androidx.camera.core.impl.CameraConfig;
 import androidx.camera.core.impl.CameraInternal;
+import androidx.camera.core.impl.Identifier;
 import androidx.camera.core.internal.CameraUseCaseAdapter;
 import androidx.core.util.Preconditions;
 import androidx.lifecycle.Lifecycle;
@@ -103,7 +105,10 @@
             @NonNull CameraUseCaseAdapter cameraUseCaseAdaptor) {
         LifecycleCamera lifecycleCamera;
         synchronized (mLock) {
-            Key key = Key.create(lifecycleOwner, cameraUseCaseAdaptor.getCameraId());
+            Key key = Key.create(lifecycleOwner,
+                    cameraUseCaseAdaptor.getCameraId(),
+                    cameraUseCaseAdaptor.getExtendedConfig().getCompatibilityId()
+                    );
             Preconditions.checkArgument(mCameraMap.get(key) == null, "LifecycleCamera already "
                     + "exists for the given LifecycleOwner and set of cameras");
 
@@ -132,9 +137,11 @@
      */
     @Nullable
     LifecycleCamera getLifecycleCamera(LifecycleOwner lifecycleOwner,
-            CameraUseCaseAdapter.CameraId cameraId) {
+            @NonNull CameraUseCaseAdapter.CameraId cameraId,
+            @NonNull CameraConfig cameraConfig) {
         synchronized (mLock) {
-            return mCameraMap.get(Key.create(lifecycleOwner, cameraId));
+            return mCameraMap.get(Key.create(lifecycleOwner, cameraId,
+                    cameraConfig.getCompatibilityId()));
         }
     }
 
@@ -174,7 +181,10 @@
         synchronized (mLock) {
             LifecycleOwner lifecycleOwner = lifecycleCamera.getLifecycleOwner();
             Key key = Key.create(lifecycleOwner,
-                    lifecycleCamera.getCameraUseCaseAdapter().getCameraId());
+                    lifecycleCamera.getCameraUseCaseAdapter().getCameraId(),
+                    lifecycleCamera.getCameraUseCaseAdapter()
+                            .getExtendedConfig().getCompatibilityId());
+
             LifecycleCameraRepositoryObserver observer =
                     getLifecycleCameraRepositoryObserver(lifecycleOwner);
             Set<Key> lifecycleCameraKeySet;
@@ -501,8 +511,10 @@
     @AutoValue
     abstract static class Key {
         static Key create(@NonNull LifecycleOwner lifecycleOwner,
-                @NonNull CameraUseCaseAdapter.CameraId cameraId) {
-            return new AutoValue_LifecycleCameraRepository_Key(lifecycleOwner, cameraId);
+                @NonNull CameraUseCaseAdapter.CameraId cameraId,
+                @NonNull Identifier cameraConfigId) {
+            return new AutoValue_LifecycleCameraRepository_Key(
+                    lifecycleOwner, cameraId, cameraConfigId);
         }
 
         @NonNull
@@ -510,6 +522,9 @@
 
         @NonNull
         public abstract CameraUseCaseAdapter.CameraId getCameraId();
+
+        @NonNull
+        public abstract Identifier getCameraConfigId();
     }
 
     private static class LifecycleCameraRepositoryObserver implements LifecycleObserver {
diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
index bc5ca14..9508ef1 100644
--- a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
+++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java
@@ -56,6 +56,7 @@
 import androidx.camera.core.ViewPort;
 import androidx.camera.core.concurrent.CameraCoordinator.CameraOperatingMode;
 import androidx.camera.core.impl.CameraConfig;
+import androidx.camera.core.impl.CameraConfigs;
 import androidx.camera.core.impl.CameraInternal;
 import androidx.camera.core.impl.ExtendedCameraConfigProviderStore;
 import androidx.camera.core.impl.utils.ContextUtil;
@@ -572,11 +573,16 @@
             throw new IllegalArgumentException("Provided camera selector unable to resolve a "
                     + "camera for the given use case");
         }
+
+        CameraConfig cameraConfig = getCameraConfig(cameraSelector,
+                cameraInternals.iterator().next().getCameraInfo());
+
         CameraUseCaseAdapter.CameraId cameraId =
                 CameraUseCaseAdapter.generateCameraId(cameraInternals);
 
         LifecycleCamera lifecycleCameraToBind =
-                mLifecycleCameraRepository.getLifecycleCamera(lifecycleOwner, cameraId);
+                mLifecycleCameraRepository.getLifecycleCamera(
+                        lifecycleOwner, cameraId, cameraConfig);
 
         Collection<LifecycleCamera> lifecycleCameras =
                 mLifecycleCameraRepository.getLifecycleCameras();
@@ -600,34 +606,10 @@
                             new CameraUseCaseAdapter(cameraInternals,
                                     mCameraX.getCameraFactory().getCameraCoordinator(),
                                     mCameraX.getCameraDeviceSurfaceManager(),
-                                    mCameraX.getDefaultConfigFactory()));
+                                    mCameraX.getDefaultConfigFactory(),
+                                    cameraConfig));
         }
 
-        CameraConfig cameraConfig = null;
-
-        // Retrieves extended camera configs from ExtendedCameraConfigProviderStore
-        for (CameraFilter cameraFilter : cameraSelector.getCameraFilterSet()) {
-            if (cameraFilter.getIdentifier() != CameraFilter.DEFAULT_ID) {
-                CameraConfig extendedCameraConfig =
-                        ExtendedCameraConfigProviderStore.getConfigProvider(
-                                cameraFilter.getIdentifier()).getConfig(
-                                lifecycleCameraToBind.getCameraInfo(), mContext);
-                if (extendedCameraConfig == null) { // ignore IDs unrelated to camera configs.
-                    continue;
-                }
-
-                // Only allows one camera config now.
-                if (cameraConfig != null) {
-                    throw new IllegalArgumentException(
-                            "Cannot apply multiple extended camera configs at the same time.");
-                }
-                cameraConfig = extendedCameraConfig;
-            }
-        }
-
-        // Applies extended camera configs to the camera
-        lifecycleCameraToBind.setExtendedConfig(cameraConfig);
-
         if (useCases.length == 0) {
             return lifecycleCameraToBind;
         }
@@ -642,6 +624,35 @@
         return lifecycleCameraToBind;
     }
 
+    @NonNull
+    private CameraConfig getCameraConfig(@NonNull CameraSelector cameraSelector,
+            @NonNull CameraInfo cameraInfo) {
+        CameraConfig cameraConfig = null;
+        for (CameraFilter cameraFilter : cameraSelector.getCameraFilterSet()) {
+            if (cameraFilter.getIdentifier() != CameraFilter.DEFAULT_ID) {
+                CameraConfig extendedCameraConfig =
+                        ExtendedCameraConfigProviderStore
+                                .getConfigProvider(cameraFilter.getIdentifier())
+                                .getConfig(cameraInfo, mContext);
+                if (extendedCameraConfig == null) { // ignore IDs unrelated to camera configs.
+                    continue;
+                }
+
+                // Only allows one camera config now.
+                if (cameraConfig != null) {
+                    throw new IllegalArgumentException(
+                            "Cannot apply multiple extended camera configs at the same time.");
+                }
+                cameraConfig = extendedCameraConfig;
+            }
+        }
+
+        if (cameraConfig == null) {
+            cameraConfig = CameraConfigs.defaultConfig();
+        }
+        return cameraConfig;
+    }
+
     /**
      * Returns true if the {@link UseCase} is bound to a lifecycle. Otherwise returns false.
      *
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCamera.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCamera.java
index 7a89cc5..dcee293 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCamera.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCamera.java
@@ -76,7 +76,7 @@
 
     private List<DeferrableSurface> mConfiguredDeferrableSurfaces = Collections.emptyList();
 
-    private CameraConfig mCameraConfig = CameraConfigs.emptyConfig();
+    private CameraConfig mCameraConfig = CameraConfigs.defaultConfig();
 
     public FakeCamera() {
         this(DEFAULT_CAMERA_ID, /*cameraControl=*/null,
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CameraUtil.java b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CameraUtil.java
index a372b22..ec89c70 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CameraUtil.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CameraUtil.java
@@ -56,6 +56,8 @@
 import androidx.camera.core.Logger;
 import androidx.camera.core.UseCase;
 import androidx.camera.core.concurrent.CameraCoordinator;
+import androidx.camera.core.impl.CameraConfig;
+import androidx.camera.core.impl.CameraConfigs;
 import androidx.camera.core.impl.CameraInternal;
 import androidx.camera.core.impl.utils.futures.Futures;
 import androidx.camera.core.internal.CameraUseCaseAdapter;
@@ -620,7 +622,8 @@
     public static CameraUseCaseAdapter createCameraUseCaseAdapter(
             @NonNull Context context,
             @NonNull CameraCoordinator cameraCoordinator,
-            @NonNull CameraSelector cameraSelector) {
+            @NonNull CameraSelector cameraSelector,
+            @NonNull CameraConfig cameraConfig) {
         try {
             CameraX cameraX = CameraXUtil.getOrCreateInstance(context, null).get(5000,
                     TimeUnit.MILLISECONDS);
@@ -629,7 +632,8 @@
             return new CameraUseCaseAdapter(cameras,
                     cameraCoordinator,
                     cameraX.getCameraDeviceSurfaceManager(),
-                    cameraX.getDefaultConfigFactory());
+                    cameraX.getDefaultConfigFactory(),
+                    cameraConfig);
         } catch (ExecutionException | InterruptedException | TimeoutException e) {
             throw new RuntimeException("Unable to retrieve CameraX instance");
         }
@@ -655,7 +659,22 @@
     public static CameraUseCaseAdapter createCameraUseCaseAdapter(
             @NonNull Context context,
             @NonNull CameraSelector cameraSelector) {
-        return createCameraUseCaseAdapter(context, new FakeCameraCoordinator(), cameraSelector);
+        return createCameraUseCaseAdapter(context, new FakeCameraCoordinator(),
+                cameraSelector, CameraConfigs.defaultConfig());
+    }
+
+    /**
+     * Creates the CameraUseCaseAdapter that would be created with the given CameraSelector and
+     * CameraConfig
+     */
+    @VisibleForTesting
+    @NonNull
+    public static CameraUseCaseAdapter createCameraUseCaseAdapter(
+            @NonNull Context context,
+            @NonNull CameraSelector cameraSelector,
+            @NonNull CameraConfig cameraConfig) {
+        return createCameraUseCaseAdapter(context, new FakeCameraCoordinator(),
+                cameraSelector, cameraConfig);
     }
 
     /**
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CoreAppTestUtil.java b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CoreAppTestUtil.java
index 206ff7d..4a33a63 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CoreAppTestUtil.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/CoreAppTestUtil.java
@@ -49,6 +49,8 @@
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public final class CoreAppTestUtil {
 
+    private static final String TAG = "CoreAppTestUtil";
+
     /** ADB shell input key code for dismissing keyguard for device with API level <= 22. */
     private static final int DISMISS_LOCK_SCREEN_CODE = 82;
     /** ADB shell command for dismissing keyguard for device with API level >= 23. */
@@ -153,8 +155,11 @@
         }
 
         device.pressHome();
-        device.waitForIdle(MAX_TIMEOUT_MS);
-
+        try {
+            device.waitForIdle(MAX_TIMEOUT_MS);
+        } catch (IllegalStateException e) {
+            Logger.d(TAG, "Fail to waitForIdle", e);
+        }
         // Close system dialogs first to avoid interrupt.
         if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
             instrumentation.getTargetContext().sendBroadcast(
@@ -205,7 +210,7 @@
             instrumentation.waitForIdleSync();
 
             if (activityRef == null) {
-                Logger.d("CoreAppTestUtil", String.format("Activity %s, failed to launch",
+                Logger.d(TAG, String.format("Activity %s, failed to launch",
                         startIntent.getComponent()) + ", ignore the foreground checking");
                 return;
             }
@@ -216,7 +221,7 @@
             Espresso.onIdle();
             return;
         } catch (Exception e) {
-            Logger.d("CoreAppTestUtil", "Fail to get foreground", e);
+            Logger.d(TAG, "Fail to get foreground", e);
         } finally {
             if (activityRef != null) {
                 IdlingRegistry.getInstance().unregister(activityRef.getViewReadyIdlingResource());
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/WakelockEmptyActivityRule.kt b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/WakelockEmptyActivityRule.kt
index db5fffe..7acb575 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/impl/WakelockEmptyActivityRule.kt
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/impl/WakelockEmptyActivityRule.kt
@@ -26,6 +26,7 @@
 import androidx.camera.core.Logger
 import androidx.camera.testing.impl.Api27Impl.setShowWhenLocked
 import androidx.camera.testing.impl.Api27Impl.setTurnScreenOn
+import androidx.camera.testing.impl.CoreAppTestUtil.clearDeviceUI
 import androidx.camera.testing.impl.activity.EmptyActivity
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.platform.app.InstrumentationRegistry
@@ -42,6 +43,7 @@
         object : Statement() {
             override fun evaluate() {
                 val instrumentation = InstrumentationRegistry.getInstrumentation()
+                clearDeviceUI(instrumentation)
                 var activityRef: EmptyActivity? = null
                 try {
                     activityRef = CoreAppTestUtil.launchActivity(
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
index 8b56757..16792c2 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
@@ -44,6 +44,7 @@
 import androidx.camera.core.ImageCapture
 import androidx.camera.core.ImageCaptureException
 import androidx.camera.core.Preview
+import androidx.camera.core.UseCase
 import androidx.camera.core.impl.utils.AspectRatioUtil
 import androidx.camera.core.impl.utils.TransformUtils.is90or270
 import androidx.camera.core.impl.utils.TransformUtils.rectToSize
@@ -271,7 +272,7 @@
 
         // Verify.
         val metadataRotation2 = cameraInfo.getSensorRotationDegrees(targetRotation2).let {
-            if (videoCapture.node != null) {
+            if (isSurfaceProcessingEnabled(videoCapture)) {
                 // If effect is enabled, the rotation should eliminate the video content rotation.
                 it - videoContentRotation
             } else it
@@ -408,6 +409,11 @@
             )
         }
 
+        // TODO(b/264936115): In stream sharing (VirtualCameraAdapter), children's ViewPortCropRect
+        //  is ignored and override to the parent size, the cropRect is also rotated. Skip the test
+        //  for now.
+        assumeTrue(!isStreamSharingEnabled(videoCapture))
+
         val file = File.createTempFile("video_", ".tmp").apply { deleteOnExit() }
 
         latchForVideoSaved = CountDownLatch(1)
@@ -1200,7 +1206,7 @@
         cameraInfo: CameraInfo
     ): ExpectedRotation {
         val rotationNeeded = cameraInfo.getSensorRotationDegrees(videoCapture.targetRotation)
-        return if (videoCapture.node != null) {
+        return if (isSurfaceProcessingEnabled(videoCapture)) {
             ExpectedRotation(rotationNeeded, 0)
         } else {
             ExpectedRotation(0, rotationNeeded)
@@ -1323,6 +1329,11 @@
         assumeExtraCroppingQuirk(implName)
     }
 
+    private fun isStreamSharingEnabled(useCase: UseCase) = !useCase.camera!!.hasTransform
+
+    private fun isSurfaceProcessingEnabled(videoCapture: VideoCapture<*>) =
+        videoCapture.node != null || isStreamSharingEnabled(videoCapture)
+
     private class ImageSavedCallback :
         ImageCapture.OnImageSavedCallback {
 
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/RecorderVideoCapabilities.java b/camera/camera-video/src/main/java/androidx/camera/video/RecorderVideoCapabilities.java
index 9d2eb01..3628e69 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/RecorderVideoCapabilities.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/RecorderVideoCapabilities.java
@@ -49,6 +49,7 @@
 import androidx.camera.video.internal.compat.quirk.DeviceQuirks;
 import androidx.camera.video.internal.encoder.VideoEncoderConfig;
 import androidx.camera.video.internal.encoder.VideoEncoderInfo;
+import androidx.camera.video.internal.workaround.QualityAddedEncoderProfilesProvider;
 import androidx.camera.video.internal.workaround.QualityResolutionModifiedEncoderProfilesProvider;
 import androidx.camera.video.internal.workaround.QualityValidatedEncoderProfilesProvider;
 
@@ -105,6 +106,11 @@
                 "Not a supported video capabilities source: " + videoCapabilitiesSource);
         EncoderProfilesProvider encoderProfilesProvider = cameraInfo.getEncoderProfilesProvider();
 
+        Quirks deviceQuirks = DeviceQuirks.getAll();
+        // Add extra supported quality.
+        encoderProfilesProvider = new QualityAddedEncoderProfilesProvider(encoderProfilesProvider,
+                deviceQuirks, cameraInfo, videoEncoderInfoFinder);
+
         if (videoCapabilitiesSource == VIDEO_CAPABILITIES_SOURCE_CODEC_CAPABILITIES) {
             encoderProfilesProvider = new QualityExploredEncoderProfilesProvider(
                     encoderProfilesProvider,
@@ -115,7 +121,6 @@
         }
 
         // Modify qualities' matching resolution to the value supported by camera.
-        Quirks deviceQuirks = DeviceQuirks.getAll();
         encoderProfilesProvider = new QualityResolutionModifiedEncoderProfilesProvider(
                 encoderProfilesProvider, deviceQuirks);
 
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/QualityExploredEncoderProfilesProvider.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/QualityExploredEncoderProfilesProvider.java
index 84faba9..f2ac9c5 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/internal/QualityExploredEncoderProfilesProvider.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/QualityExploredEncoderProfilesProvider.java
@@ -19,6 +19,7 @@
 import static androidx.camera.core.internal.utils.SizeUtil.findNearestHigherFor;
 import static androidx.camera.video.internal.config.VideoConfigUtil.toVideoEncoderConfig;
 import static androidx.camera.video.internal.utils.DynamicRangeUtil.isHdrSettingsMatched;
+import static androidx.camera.video.internal.utils.EncoderProfilesUtil.deriveVideoProfile;
 import static androidx.core.util.Preconditions.checkArgument;
 
 import static java.util.Objects.requireNonNull;
@@ -37,7 +38,6 @@
 import androidx.camera.core.impl.utils.CompareSizesByArea;
 import androidx.camera.video.CapabilitiesByQuality;
 import androidx.camera.video.Quality;
-import androidx.camera.video.internal.config.VideoConfigUtil;
 import androidx.camera.video.internal.encoder.VideoEncoderConfig;
 import androidx.camera.video.internal.encoder.VideoEncoderInfo;
 
@@ -170,7 +170,8 @@
                         encoderProfiles);
                 // Generate VideoProfile from base VideoProfile and new size.
                 generatedVideoProfiles.add(
-                        generateVideoProfile(baseVideoProfile, size, encoderInfo));
+                        deriveVideoProfile(baseVideoProfile, size,
+                                encoderInfo.getSupportedBitrateRange()));
             }
             if (!generatedVideoProfiles.isEmpty()) {
                 // Use the nearest higher EncoderProfiles as base EncoderProfiles.
@@ -210,31 +211,6 @@
         return capabilities;
     }
 
-    @NonNull
-    private static VideoProfileProxy generateVideoProfile(@NonNull VideoProfileProxy baseProfile,
-            @NonNull Size size, @NonNull VideoEncoderInfo encoderInfo) {
-        // "Guess" bit rate. Scale the bitrate based on size difference.
-        int derivedBitrate = VideoConfigUtil.scaleAndClampBitrate(
-                baseProfile.getBitrate(),
-                baseProfile.getBitDepth(), baseProfile.getBitDepth(),
-                baseProfile.getFrameRate(), baseProfile.getFrameRate(),
-                size.getWidth(), baseProfile.getWidth(),
-                size.getHeight(), baseProfile.getHeight(),
-                encoderInfo.getSupportedBitrateRange());
-        return VideoProfileProxy.create(
-                baseProfile.getCodec(),
-                baseProfile.getMediaType(),
-                derivedBitrate,
-                baseProfile.getFrameRate(),
-                size.getWidth(),
-                size.getHeight(),
-                baseProfile.getProfile(),
-                baseProfile.getBitDepth(),
-                baseProfile.getChromaSubsampling(),
-                baseProfile.getHdrFormat()
-        );
-    }
-
     @Nullable
     private static EncoderProfilesProxy mergeEncoderProfiles(
             @Nullable EncoderProfilesProxy baseProfiles,
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/DeviceQuirksLoader.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/DeviceQuirksLoader.java
index 0234352..910c92b 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/DeviceQuirksLoader.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/DeviceQuirksLoader.java
@@ -101,6 +101,9 @@
         if (StopCodecAfterSurfaceRemovalCrashMediaServerQuirk.load()) {
             quirks.add(new StopCodecAfterSurfaceRemovalCrashMediaServerQuirk());
         }
+        if (ExtraSupportedQualityQuirk.load()) {
+            quirks.add(new ExtraSupportedQualityQuirk());
+        }
 
         return quirks;
     }
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/ExtraSupportedQualityQuirk.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/ExtraSupportedQualityQuirk.java
new file mode 100644
index 0000000..08fa121
--- /dev/null
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/compat/quirk/ExtraSupportedQualityQuirk.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.video.internal.compat.quirk;
+
+import static android.media.CamcorderProfile.QUALITY_480P;
+import static android.media.CamcorderProfile.QUALITY_HIGH;
+
+import static androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_480P;
+import static androidx.camera.core.internal.utils.SizeUtil.getArea;
+import static androidx.camera.video.internal.config.VideoConfigUtil.toVideoEncoderConfig;
+import static androidx.camera.video.internal.utils.EncoderProfilesUtil.deriveVideoProfile;
+import static androidx.camera.video.internal.utils.EncoderProfilesUtil.getFirstVideoProfile;
+
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.singletonList;
+
+import android.os.Build;
+import android.util.Range;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.arch.core.util.Function;
+import androidx.camera.core.impl.CameraInfoInternal;
+import androidx.camera.core.impl.EncoderProfilesProvider;
+import androidx.camera.core.impl.EncoderProfilesProxy;
+import androidx.camera.core.impl.Quirk;
+import androidx.camera.video.VideoSpec;
+import androidx.camera.video.internal.encoder.VideoEncoderConfig;
+import androidx.camera.video.internal.encoder.VideoEncoderInfo;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * <p>QuirkSummary
+ *     Bug Id: b/311311853
+ *     Description: MotoC doesn't have any supported Quality for front camera. The reason is
+ *                  that the highest supported CamcorderProfile is QUALITY_CIF(352x288).
+ *                  By experimental result, QUALITY_480P(720x480) can be used to record video so
+ *                  we can add at least one supported quality.
+ *                  In addition, MotoC only has two camera id, "0" for rear and "1" for front, so
+ *                  it is feasible to simply check camera id "1" to create EncoderProfilesProxy.
+ *     Device(s): moto c
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class ExtraSupportedQualityQuirk implements Quirk {
+    private static final String MOTO_C_FRONT_CAM_ID = "1";
+
+    static boolean load() {
+        return isMotoC();
+    }
+
+    private static boolean isMotoC() {
+        return "motorola".equalsIgnoreCase(Build.BRAND) && "moto c".equalsIgnoreCase(Build.MODEL);
+    }
+
+    /** Gets the EncoderProfilesProxy for the extra supported quality. */
+    @Nullable
+    public Map<Integer, EncoderProfilesProxy> getExtraEncoderProfiles(
+            @NonNull CameraInfoInternal cameraInfo,
+            @NonNull EncoderProfilesProvider encoderProfilesProvider,
+            @NonNull Function<VideoEncoderConfig, VideoEncoderInfo> videoEncoderInfoFinder) {
+        if (isMotoC()) {
+            return getExtraEncoderProfilesForMotoC(cameraInfo, encoderProfilesProvider,
+                    videoEncoderInfoFinder);
+        }
+        return emptyMap();
+    }
+
+    // Create 480P EncoderProfiles for front Camera if not exist. Derive profile from QUALITY_HIGH
+    // which should be QUALITY_CIF. Even if QUALITY_HIGH is not QUALITY_CIF due to ROM
+    // update, the code should still work.
+    @Nullable
+    private Map<Integer, EncoderProfilesProxy> getExtraEncoderProfilesForMotoC(
+            @NonNull CameraInfoInternal cameraInfo,
+            @NonNull EncoderProfilesProvider encoderProfilesProvider,
+            @NonNull Function<VideoEncoderConfig, VideoEncoderInfo> videoEncoderInfoFinder) {
+        if (!MOTO_C_FRONT_CAM_ID.equals(cameraInfo.getCameraId())
+                || encoderProfilesProvider.hasProfile(QUALITY_480P)) {
+            return null;
+        }
+
+        // Derive from QUALITY_HIGH
+        EncoderProfilesProxy profilesHigh = encoderProfilesProvider.getAll(QUALITY_HIGH);
+        EncoderProfilesProxy.VideoProfileProxy videoProfileHigh =
+                getFirstVideoProfile(profilesHigh);
+        if (videoProfileHigh == null) {
+            return null;
+        }
+        Range<Integer> supportedBitrateRange =
+                getSupportedBitrateRange(videoProfileHigh, videoEncoderInfoFinder);
+        EncoderProfilesProxy.VideoProfileProxy derivedVideoProfile =
+                deriveVideoProfile(videoProfileHigh, RESOLUTION_480P, supportedBitrateRange);
+        EncoderProfilesProxy profiles480p =
+                EncoderProfilesProxy.ImmutableEncoderProfilesProxy.create(
+                        profilesHigh.getDefaultDurationSeconds(),
+                        profilesHigh.getRecommendedFileFormat(),
+                        profilesHigh.getAudioProfiles(),
+                        singletonList(derivedVideoProfile));
+
+        // Return mapping
+        Map<Integer, EncoderProfilesProxy> extraEncoderProfilesMap = new HashMap<>();
+        extraEncoderProfilesMap.put(QUALITY_480P, profiles480p);
+        // Update QUALITY_HIGH if necessary.
+        Size sizeHigh = new Size(videoProfileHigh.getWidth(), videoProfileHigh.getHeight());
+        if (getArea(RESOLUTION_480P) > getArea(sizeHigh)) {
+            extraEncoderProfilesMap.put(QUALITY_HIGH, profiles480p);
+        }
+        return extraEncoderProfilesMap;
+    }
+
+    @NonNull
+    private static Range<Integer> getSupportedBitrateRange(
+            @NonNull EncoderProfilesProxy.VideoProfileProxy videoProfile,
+            @NonNull Function<VideoEncoderConfig, VideoEncoderInfo> videoEncoderInfoFinder) {
+        VideoEncoderInfo encoderInfo =
+                videoEncoderInfoFinder.apply(toVideoEncoderConfig(videoProfile));
+        return encoderInfo != null ? encoderInfo.getSupportedBitrateRange()
+                : VideoSpec.BITRATE_RANGE_AUTO;
+    }
+}
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/utils/EncoderProfilesUtil.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/utils/EncoderProfilesUtil.java
new file mode 100644
index 0000000..0f68caf
--- /dev/null
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/utils/EncoderProfilesUtil.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.video.internal.utils;
+
+import android.util.Range;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.impl.EncoderProfilesProxy;
+import androidx.camera.video.VideoSpec;
+import androidx.camera.video.internal.config.VideoConfigUtil;
+
+/** Utility class for encoder profiles related operations. */
+@RequiresApi(21)
+public class EncoderProfilesUtil {
+
+    private EncoderProfilesUtil() {
+    }
+
+    /**
+     * Derives a VideoProfile from a base VideoProfile and a new resolution.
+     *
+     * <p>Most fields are directly copied from the base VideoProfile except the bitrate, which will
+     * be scaled and clamped according to the new resolution and the given bitrate range.
+     *
+     * @param baseVideoProfile    the VideoProfile to derive.
+     * @param newResolution       the new resolution.
+     * @param bitrateRangeToClamp the bitrate range to clamp. This is usually the supported
+     *                            bitrate range of the target codec. Set
+     *                            {@link VideoSpec#BITRATE_RANGE_AUTO} as no clamp required.
+     * @return a derived VideoProfile.
+     */
+    @NonNull
+    public static EncoderProfilesProxy.VideoProfileProxy deriveVideoProfile(
+            @NonNull EncoderProfilesProxy.VideoProfileProxy baseVideoProfile,
+            @NonNull Size newResolution,
+            @NonNull Range<Integer> bitrateRangeToClamp) {
+
+        // "Guess" bit rate.
+        int derivedBitrate = VideoConfigUtil.scaleAndClampBitrate(
+                baseVideoProfile.getBitrate(),
+                baseVideoProfile.getBitDepth(), baseVideoProfile.getBitDepth(),
+                baseVideoProfile.getFrameRate(), baseVideoProfile.getFrameRate(),
+                newResolution.getWidth(), baseVideoProfile.getWidth(),
+                newResolution.getHeight(), baseVideoProfile.getHeight(),
+                bitrateRangeToClamp);
+
+        return EncoderProfilesProxy.VideoProfileProxy.create(
+                baseVideoProfile.getCodec(),
+                baseVideoProfile.getMediaType(),
+                derivedBitrate,
+                baseVideoProfile.getFrameRate(),
+                newResolution.getWidth(),
+                newResolution.getHeight(),
+                baseVideoProfile.getProfile(),
+                baseVideoProfile.getBitDepth(),
+                baseVideoProfile.getChromaSubsampling(),
+                baseVideoProfile.getHdrFormat()
+        );
+    }
+
+    /**
+     * Gets the first VideoProfile from the given EncoderProfileProxy. Returns null if
+     * encoderProfiles is null or there is no VideoProfile.
+     */
+    @Nullable
+    public static EncoderProfilesProxy.VideoProfileProxy getFirstVideoProfile(
+            @Nullable EncoderProfilesProxy encoderProfiles) {
+        if (encoderProfiles != null && !encoderProfiles.getVideoProfiles().isEmpty()) {
+            return encoderProfiles.getVideoProfiles().get(0);
+        }
+        return null;
+    }
+}
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/workaround/QualityAddedEncoderProfilesProvider.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/workaround/QualityAddedEncoderProfilesProvider.java
new file mode 100644
index 0000000..7471dad
--- /dev/null
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/workaround/QualityAddedEncoderProfilesProvider.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.video.internal.workaround;
+
+import static androidx.core.util.Preconditions.checkState;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.arch.core.util.Function;
+import androidx.camera.core.impl.CameraInfoInternal;
+import androidx.camera.core.impl.EncoderProfilesProvider;
+import androidx.camera.core.impl.EncoderProfilesProxy;
+import androidx.camera.core.impl.Quirks;
+import androidx.camera.video.internal.compat.quirk.ExtraSupportedQualityQuirk;
+import androidx.camera.video.internal.encoder.VideoEncoderConfig;
+import androidx.camera.video.internal.encoder.VideoEncoderInfo;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An implementation that adds extra supported qualities.
+ *
+ * @see ExtraSupportedQualityQuirk
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class QualityAddedEncoderProfilesProvider implements EncoderProfilesProvider {
+
+    private final EncoderProfilesProvider mProvider;
+    @Nullable
+    private Map<Integer, EncoderProfilesProxy> mExtraQualityToEncoderProfiles;
+
+    public QualityAddedEncoderProfilesProvider(
+            @NonNull EncoderProfilesProvider provider,
+            @NonNull Quirks quirks,
+            @NonNull CameraInfoInternal cameraInfo,
+            @NonNull Function<VideoEncoderConfig, VideoEncoderInfo> videoEncoderInfoFinder) {
+        mProvider = provider;
+
+        List<ExtraSupportedQualityQuirk> extraQuirks = quirks.getAll(
+                ExtraSupportedQualityQuirk.class);
+        if (!extraQuirks.isEmpty()) {
+            checkState(extraQuirks.size() == 1);
+            Map<Integer, EncoderProfilesProxy> extraEncoderProfiles = extraQuirks.get(0)
+                    .getExtraEncoderProfiles(cameraInfo, mProvider, videoEncoderInfoFinder);
+            if (extraEncoderProfiles != null) {
+                mExtraQualityToEncoderProfiles = new HashMap<>(extraEncoderProfiles);
+            }
+        }
+    }
+
+    @Override
+    public boolean hasProfile(int quality) {
+        return getProfilesInternal(quality) != null;
+    }
+
+    @Nullable
+    @Override
+    public EncoderProfilesProxy getAll(int quality) {
+        return getProfilesInternal(quality);
+    }
+
+    @Nullable
+    private EncoderProfilesProxy getProfilesInternal(int quality) {
+        if (mExtraQualityToEncoderProfiles != null
+                && mExtraQualityToEncoderProfiles.containsKey(quality)) {
+            return mExtraQualityToEncoderProfiles.get(quality);
+        }
+        return mProvider.getAll(quality);
+    }
+}
diff --git a/camera/camera-video/src/test/java/androidx/camera/video/internal/compat/quirk/ExtraSupportedQualityQuirkTest.kt b/camera/camera-video/src/test/java/androidx/camera/video/internal/compat/quirk/ExtraSupportedQualityQuirkTest.kt
new file mode 100644
index 0000000..a671e68
--- /dev/null
+++ b/camera/camera-video/src/test/java/androidx/camera/video/internal/compat/quirk/ExtraSupportedQualityQuirkTest.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.video.internal.compat.quirk
+
+import android.media.CamcorderProfile.QUALITY_480P
+import android.media.CamcorderProfile.QUALITY_CIF
+import android.media.CamcorderProfile.QUALITY_HIGH
+import android.media.CamcorderProfile.QUALITY_LOW
+import android.media.CamcorderProfile.QUALITY_QCIF
+import android.os.Build
+import android.util.Size
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.impl.EncoderProfilesProxy
+import androidx.camera.testing.fakes.FakeCameraInfoInternal
+import androidx.camera.testing.impl.EncoderProfilesUtil.DEFAULT_DURATION
+import androidx.camera.testing.impl.EncoderProfilesUtil.DEFAULT_OUTPUT_FORMAT
+import androidx.camera.testing.impl.EncoderProfilesUtil.RESOLUTION_480P
+import androidx.camera.testing.impl.EncoderProfilesUtil.RESOLUTION_CIF
+import androidx.camera.testing.impl.EncoderProfilesUtil.RESOLUTION_QCIF
+import androidx.camera.testing.impl.EncoderProfilesUtil.createFakeAudioProfileProxy
+import androidx.camera.testing.impl.EncoderProfilesUtil.createFakeVideoProfileProxy
+import androidx.camera.testing.impl.fakes.FakeEncoderProfilesProvider
+import androidx.camera.testing.impl.fakes.FakeVideoEncoderInfo
+import androidx.camera.video.internal.utils.EncoderProfilesUtil.getFirstVideoProfile
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.shadows.ShadowBuild
+
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class ExtraSupportedQualityQuirkTest {
+    companion object {
+        private const val MOTO_C_BRAND = "motorola"
+        private const val MOTO_C_MODEL = "moto c"
+    }
+
+    @Test
+    fun motoC_frontCamera_canGetExtraSupportedQuality() {
+        // Arrange: Simulate the condition of MotoC. See b/311311853.
+        ShadowBuild.setBrand(MOTO_C_BRAND)
+        ShadowBuild.setModel(MOTO_C_MODEL)
+        val cameraInfo = FakeCameraInfoInternal("1", CameraSelector.LENS_FACING_FRONT)
+        val profilesCif = EncoderProfilesProxy.ImmutableEncoderProfilesProxy.create(
+            DEFAULT_DURATION,
+            DEFAULT_OUTPUT_FORMAT,
+            listOf(createFakeAudioProfileProxy()),
+            listOf(createFakeVideoProfileProxy(
+                RESOLUTION_CIF.width,
+                RESOLUTION_CIF.height,
+            ))
+        )
+        val profilesQcif = EncoderProfilesProxy.ImmutableEncoderProfilesProxy.create(
+            DEFAULT_DURATION,
+            DEFAULT_OUTPUT_FORMAT,
+            listOf(createFakeAudioProfileProxy()),
+            listOf(createFakeVideoProfileProxy(
+                RESOLUTION_QCIF.width,
+                RESOLUTION_QCIF.height,
+            ))
+        )
+        val encoderProfileProvider = FakeEncoderProfilesProvider.Builder()
+            .add(QUALITY_HIGH, profilesCif)
+            .add(QUALITY_CIF, profilesCif)
+            .add(QUALITY_QCIF, profilesQcif)
+            .add(QUALITY_LOW, profilesQcif)
+            .build()
+
+        // Act.
+        val qualityEncoderProfilesMap = ExtraSupportedQualityQuirk()
+            .getExtraEncoderProfiles(cameraInfo, encoderProfileProvider) {
+                FakeVideoEncoderInfo()
+            }
+
+        // Assert: check QUALITY_480P
+        assertThat(qualityEncoderProfilesMap).isNotNull()
+        assertThat(qualityEncoderProfilesMap).containsKey(QUALITY_480P)
+        val profiles480p = qualityEncoderProfilesMap!![QUALITY_480P]
+        val videoProfile480p = getFirstVideoProfile(profiles480p)!!
+        assertThat(videoProfile480p.getResolution()).isEqualTo(RESOLUTION_480P)
+        // Assert: QUALITY_HIGH is the same as QUALITY_480P
+        assertThat(qualityEncoderProfilesMap).containsKey(QUALITY_HIGH)
+        assertThat(qualityEncoderProfilesMap[QUALITY_HIGH]).isEqualTo(profiles480p)
+    }
+
+    private fun EncoderProfilesProxy.VideoProfileProxy.getResolution() = Size(width, height)
+}
diff --git a/camera/camera-video/src/test/java/androidx/camera/video/internal/workaround/QualityAddedEncoderProfilesProviderTest.kt b/camera/camera-video/src/test/java/androidx/camera/video/internal/workaround/QualityAddedEncoderProfilesProviderTest.kt
new file mode 100644
index 0000000..f7dd799
--- /dev/null
+++ b/camera/camera-video/src/test/java/androidx/camera/video/internal/workaround/QualityAddedEncoderProfilesProviderTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.video.internal.workaround
+
+import android.media.CamcorderProfile.QUALITY_480P
+import android.os.Build
+import androidx.arch.core.util.Function
+import androidx.camera.core.impl.CameraInfoInternal
+import androidx.camera.core.impl.EncoderProfilesProvider
+import androidx.camera.core.impl.EncoderProfilesProxy
+import androidx.camera.core.impl.EncoderProfilesProxy.ImmutableEncoderProfilesProxy
+import androidx.camera.core.impl.Quirks
+import androidx.camera.testing.fakes.FakeCameraInfoInternal
+import androidx.camera.testing.impl.EncoderProfilesUtil.DEFAULT_DURATION
+import androidx.camera.testing.impl.EncoderProfilesUtil.DEFAULT_OUTPUT_FORMAT
+import androidx.camera.testing.impl.EncoderProfilesUtil.RESOLUTION_480P
+import androidx.camera.testing.impl.EncoderProfilesUtil.createFakeAudioProfileProxy
+import androidx.camera.testing.impl.EncoderProfilesUtil.createFakeVideoProfileProxy
+import androidx.camera.testing.impl.fakes.FakeEncoderProfilesProvider
+import androidx.camera.testing.impl.fakes.FakeVideoEncoderInfo
+import androidx.camera.video.internal.compat.quirk.ExtraSupportedQualityQuirk
+import androidx.camera.video.internal.encoder.VideoEncoderConfig
+import androidx.camera.video.internal.encoder.VideoEncoderInfo
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class QualityAddedEncoderProfilesProviderTest {
+
+    @Test
+    fun canSupportExtraQuality() {
+        // Arrange.
+        val baseProvider = FakeEncoderProfilesProvider.Builder().build()
+        val encoderProfiles = ImmutableEncoderProfilesProxy.create(
+            DEFAULT_DURATION,
+            DEFAULT_OUTPUT_FORMAT,
+            listOf(createFakeAudioProfileProxy()),
+            listOf(createFakeVideoProfileProxy(RESOLUTION_480P.width, RESOLUTION_480P.height)),
+        )
+        val quirks = Quirks(listOf(FakeQuirk(mapOf(QUALITY_480P to encoderProfiles))))
+        val cameraInfo = FakeCameraInfoInternal()
+        val encoderInfo = FakeVideoEncoderInfo()
+
+        // Act.
+        val provider = QualityAddedEncoderProfilesProvider(baseProvider, quirks, cameraInfo) {
+            encoderInfo
+        }
+
+        // Assert.
+        assertThat(provider.getAll(QUALITY_480P)).isNotNull()
+    }
+
+    private class FakeQuirk(private val qualityToEncoderProfiles: Map<Int, EncoderProfilesProxy>) :
+        ExtraSupportedQualityQuirk() {
+
+        override fun getExtraEncoderProfiles(
+            cameraInfo: CameraInfoInternal,
+            encoderProfilesProvider: EncoderProfilesProvider,
+            videoEncoderInfoFinder: Function<VideoEncoderConfig, VideoEncoderInfo>
+        ) = qualityToEncoderProfiles
+    }
+}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java b/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
index 72d6808..518c7ae 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java
@@ -836,7 +836,7 @@
      *
      * <p>The saved image is cropped to match the aspect ratio of the {@link PreviewView}. To
      * take a picture with the maximum available resolution, make sure that the
-     * {@link PreviewView}'s aspect ratio is 4:3.
+     * {@link PreviewView}'s aspect ratio matches the max JPEG resolution supported by the camera.
      *
      * @param outputFileOptions  Options to store the newly captured image.
      * @param executor           The executor in which the callback methods will be run.
diff --git a/camera/camera-viewfinder-core/src/main/java/androidx/viewfinder/core/ZoomGestureDetector.java b/camera/camera-viewfinder-core/src/main/java/androidx/viewfinder/core/ZoomGestureDetector.java
deleted file mode 100644
index 1888109..0000000
--- a/camera/camera-viewfinder-core/src/main/java/androidx/viewfinder/core/ZoomGestureDetector.java
+++ /dev/null
@@ -1,591 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.viewfinder.core;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.os.Build;
-import android.os.Handler;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.RestrictTo;
-
-/**
- * Detects scaling transformation gestures that interprets zooming events using the supplied
- * {@link MotionEvent}s.
- *
- * <p>The {@link OnZoomGestureListener} callback will notify users when a particular
- * gesture event has occurred.
- *
- * <p>This class should only be used with {@link MotionEvent}s reported via touch.
- *
- * <p>To use this class:
- * <ul>
- *  <li>Create an instance of the {@code ZoomGestureDetector} for your
- *      {@link View}
- *  <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
- *          {@link #onTouchEvent(MotionEvent)}. The methods defined in your
- *          callback will be executed when the events occur.
- * </ul>
- */
-// TODO(b/314701735): update the documentation with examples using camera classes.
-// TODO(b/314701401): convert to kotlin implementation.
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-public class ZoomGestureDetector {
-    private static final String TAG = "ZoomGestureDetector";
-    // The default minimum span that the detector interprets a zooming event with. It's set to 0
-    // to give the most responsiveness.
-    // TODO(b/314702145): define a different span if appropriate.
-    private static final int DEFAULT_MIN_SPAN = 0;
-
-    /**
-     * The listener for receiving notifications when gestures occur.
-     * If you want to listen for all the different gestures then implement
-     * this interface.
-     *
-     * <p>An application will receive events in the following order:
-     * <ul>
-     *  <li>One {@link OnZoomGestureListener#onZoomBegin(ZoomGestureDetector)}
-     *  <li>Zero or more {@link OnZoomGestureListener#onZoom(ZoomGestureDetector)}
-     *  <li>One {@link OnZoomGestureListener#onZoomEnd(ZoomGestureDetector)}
-     * </ul>
-     */
-    public interface OnZoomGestureListener {
-        /**
-         * Responds to zooming events for a gesture in progress.
-         * Reported by pointer motion.
-         *
-         * @param detector The detector reporting the event - use this to
-         *          retrieve extended info about event state.
-         * @return Whether or not the detector should consider this event
-         *          as handled. If an event was not handled, the detector
-         *          will continue to accumulate movement until an event is
-         *          handled. This can be useful if an application, for example,
-         *          only wants to update scaling factors if the change is
-         *          greater than 0.01.
-         */
-        default boolean onZoom(@NonNull ZoomGestureDetector detector) {
-            return false;
-        }
-
-        /**
-         * Responds to the beginning of a zooming gesture. Reported by
-         * new pointers going down.
-         *
-         * @param detector The detector reporting the event - use this to
-         *          retrieve extended info about event state.
-         * @return Whether or not the detector should continue recognizing
-         *          this gesture. For example, if a gesture is beginning
-         *          with a focal point outside of a region where it makes
-         *          sense, onZoomBegin() may return false to ignore the
-         *          rest of the gesture.
-         */
-        default boolean onZoomBegin(@NonNull ZoomGestureDetector detector) {
-            return true;
-        }
-
-        /**
-         * Responds to the end of a zoom gesture. Reported by existing
-         * pointers going up.
-         *
-         * <p>Once a zoom has ended, {@link ZoomGestureDetector#getFocusX()}
-         * and {@link ZoomGestureDetector#getFocusY()} will return focal point
-         * of the pointers remaining on the screen.
-         *
-         * @param detector The detector reporting the event - use this to
-         *          retrieve extended info about event state.
-         */
-        default void onZoomEnd(@NonNull ZoomGestureDetector detector) {
-            // Intentionally empty
-        }
-    }
-
-    private final Context mContext;
-    private final OnZoomGestureListener mListener;
-
-    private float mFocusX;
-    private float mFocusY;
-
-    private boolean mQuickZoomEnabled;
-    private boolean mStylusZoomEnabled;
-
-    private float mCurrSpan;
-    private float mPrevSpan;
-    private float mInitialSpan;
-    private float mCurrSpanX;
-    private float mCurrSpanY;
-    private float mPrevSpanX;
-    private float mPrevSpanY;
-    private long mCurrTime;
-    private long mPrevTime;
-    private boolean mInProgress;
-    private int mSpanSlop;
-
-    private int mMinSpan;
-
-    private final Handler mHandler;
-
-    private float mAnchoredZoomStartX;
-    private float mAnchoredZoomStartY;
-    private int mAnchoredZoomMode = ANCHORED_ZOOM_MODE_NONE;
-
-    private static final float SCALE_FACTOR = .5f;
-    private static final int ANCHORED_ZOOM_MODE_NONE = 0;
-    private static final int ANCHORED_ZOOM_MODE_DOUBLE_TAP = 1;
-    private static final int ANCHORED_ZOOM_MODE_STYLUS = 2;
-    private GestureDetector mGestureDetector;
-
-    private boolean mEventBeforeOrAboveStartingGestureEvent;
-
-    /**
-     * Creates a ZoomGestureDetector with the supplied listener.
-     * You may only use this constructor from a {@link android.os.Looper Looper} thread.
-     *
-     * @param context the application's context
-     * @param listener the listener invoked for all the callbacks, this must
-     * not be null.
-     *
-     * @throws NullPointerException if {@code listener} is null.
-     */
-    public ZoomGestureDetector(@NonNull Context context,
-            @NonNull OnZoomGestureListener listener) {
-        this(context, null, listener);
-    }
-
-    /**
-     * Creates a ZoomGestureDetector with the supplied listener.
-     * @see android.os.Handler#Handler()
-     *
-     * @param context the application's context
-     * @param listener the listener invoked for all the callbacks, this must
-     * not be null.
-     * @param handler the handler to use for running deferred listener events.
-     *
-     * @throws NullPointerException if {@code listener} is null.
-     */
-    public ZoomGestureDetector(@NonNull Context context, @Nullable Handler handler,
-            @NonNull OnZoomGestureListener listener) {
-        this(context, ViewConfiguration.get(context).getScaledTouchSlop() * 2,
-                DEFAULT_MIN_SPAN, handler, listener);
-    }
-
-    /**
-     * Creates a ZoomGestureDetector with span slop and min span.
-     *
-     * @param context the application's context.
-     * @param spanSlop the threshold for interpreting a touch movement as zooming.
-     * @param minSpan the minimum threshold of zooming span. The span could be
-     *                overridden by other usages to specify a different zooming span, for instance,
-     *                if you need pinch gestures to continue closer together than the default.
-     * @param listener the listener invoked for all the callbacks, this must not be null.
-     * @param handler the handler to use for running deferred listener events.
-     *
-     * @throws NullPointerException if {@code listener} is null.
-     */
-    @SuppressLint("ExecutorRegistration")
-    public ZoomGestureDetector(@NonNull Context context, int spanSlop,
-            int minSpan, @Nullable Handler handler,
-            @NonNull OnZoomGestureListener listener) {
-        mContext = context;
-        mListener = listener;
-        mSpanSlop = spanSlop;
-        mMinSpan = minSpan;
-        mHandler = handler;
-        // Quick zoom is enabled by default after JB_MR2
-        final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
-        if (targetSdkVersion > Build.VERSION_CODES.JELLY_BEAN_MR2) {
-            setQuickZoomEnabled(true);
-        }
-        // Stylus zoom is enabled by default after LOLLIPOP_MR1
-        if (targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
-            setStylusZoomEnabled(true);
-        }
-    }
-
-    /**
-     * Accepts MotionEvents and dispatches events to a {@link OnZoomGestureListener}
-     * when appropriate.
-     *
-     * <p>Applications should pass a complete and consistent event stream to this method.
-     * A complete and consistent event stream involves all MotionEvents from the initial
-     * ACTION_DOWN to the final ACTION_UP or ACTION_CANCEL.</p>
-     *
-     * @param event The event to process
-     * @return true if the event was processed and the detector wants to receive the
-     *         rest of the MotionEvents in this event stream.
-     */
-    public boolean onTouchEvent(@NonNull MotionEvent event) {
-        mCurrTime = event.getEventTime();
-
-        final int action = event.getActionMasked();
-
-        // Forward the event to check for double tap gesture
-        if (mQuickZoomEnabled) {
-            mGestureDetector.onTouchEvent(event);
-        }
-
-        final int count = event.getPointerCount();
-        final boolean isStylusButtonDown =
-                (event.getButtonState() & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0;
-
-        final boolean anchoredZoomCancelled =
-                mAnchoredZoomMode == ANCHORED_ZOOM_MODE_STYLUS && !isStylusButtonDown;
-        final boolean streamComplete = action == MotionEvent.ACTION_UP
-                || action == MotionEvent.ACTION_CANCEL
-                || anchoredZoomCancelled;
-
-        if (action == MotionEvent.ACTION_DOWN || streamComplete) {
-            // Reset any scale in progress with the listener.
-            // If it's an ACTION_DOWN we're beginning a new event stream.
-            // This means the app probably didn't give us all the events. Shame on it.
-            if (mInProgress) {
-                mListener.onZoomEnd(this);
-                mInProgress = false;
-                mInitialSpan = 0;
-                mAnchoredZoomMode = ANCHORED_ZOOM_MODE_NONE;
-            } else if (inAnchoredZoomMode() && streamComplete) {
-                mInProgress = false;
-                mInitialSpan = 0;
-                mAnchoredZoomMode = ANCHORED_ZOOM_MODE_NONE;
-            }
-
-            if (streamComplete) {
-                return true;
-            }
-        }
-
-        if (!mInProgress && mStylusZoomEnabled && !inAnchoredZoomMode()
-                && !streamComplete && isStylusButtonDown) {
-            // Start of a button zoom gesture
-            mAnchoredZoomStartX = event.getX();
-            mAnchoredZoomStartY = event.getY();
-            mAnchoredZoomMode = ANCHORED_ZOOM_MODE_STYLUS;
-            mInitialSpan = 0;
-        }
-
-        final boolean configChanged = action == MotionEvent.ACTION_DOWN
-                || action == MotionEvent.ACTION_POINTER_UP
-                || action == MotionEvent.ACTION_POINTER_DOWN
-                || anchoredZoomCancelled;
-
-        final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
-        final int skipIndex = pointerUp ? event.getActionIndex() : -1;
-
-        // Determine focal point
-        float sumX = 0, sumY = 0;
-        final int div = pointerUp ? count - 1 : count;
-        final float focusX;
-        final float focusY;
-        if (inAnchoredZoomMode()) {
-            // In anchored scale mode, the focal pt is always where the double tap
-            // or button down gesture started
-            focusX = mAnchoredZoomStartX;
-            focusY = mAnchoredZoomStartY;
-            if (event.getY() < focusY) {
-                mEventBeforeOrAboveStartingGestureEvent = true;
-            } else {
-                mEventBeforeOrAboveStartingGestureEvent = false;
-            }
-        } else {
-            for (int i = 0; i < count; i++) {
-                if (skipIndex == i) continue;
-                sumX += event.getX(i);
-                sumY += event.getY(i);
-            }
-
-            focusX = sumX / div;
-            focusY = sumY / div;
-        }
-
-        // Determine average deviation from focal point
-        float devSumX = 0, devSumY = 0;
-        for (int i = 0; i < count; i++) {
-            if (skipIndex == i) continue;
-
-            // Convert the resulting diameter into a radius.
-            devSumX += Math.abs(event.getX(i) - focusX);
-            devSumY += Math.abs(event.getY(i) - focusY);
-        }
-        final float devX = devSumX / div;
-        final float devY = devSumY / div;
-
-        // Span is the average distance between touch points through the focal point;
-        // i.e. the diameter of the circle with a radius of the average deviation from
-        // the focal point.
-        final float spanX = devX * 2;
-        final float spanY = devY * 2;
-        final float span;
-        if (inAnchoredZoomMode()) {
-            span = spanY;
-        } else {
-            span = (float) Math.hypot(spanX, spanY);
-        }
-
-        // Dispatch begin/end events as needed.
-        // If the configuration changes, notify the app to reset its current state by beginning
-        // a fresh zoom event stream.
-        final boolean wasInProgress = mInProgress;
-        mFocusX = focusX;
-        mFocusY = focusY;
-        if (!inAnchoredZoomMode() && mInProgress && (span < mMinSpan || configChanged)) {
-            mListener.onZoomEnd(this);
-            mInProgress = false;
-            mInitialSpan = span;
-        }
-        if (configChanged) {
-            mPrevSpanX = mCurrSpanX = spanX;
-            mPrevSpanY = mCurrSpanY = spanY;
-            mInitialSpan = mPrevSpan = mCurrSpan = span;
-        }
-
-        final int minSpan = inAnchoredZoomMode() ? mSpanSlop : mMinSpan;
-        if (!mInProgress && span >=  minSpan
-                && (wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) {
-            mPrevSpanX = mCurrSpanX = spanX;
-            mPrevSpanY = mCurrSpanY = spanY;
-            mPrevSpan = mCurrSpan = span;
-            mPrevTime = mCurrTime;
-            mInProgress = mListener.onZoomBegin(this);
-        }
-
-        // Handle motion; focal point and span/scale factor are changing.
-        if (action == MotionEvent.ACTION_MOVE) {
-            mCurrSpanX = spanX;
-            mCurrSpanY = spanY;
-            mCurrSpan = span;
-
-            boolean updatePrev = true;
-
-            if (mInProgress) {
-                updatePrev = mListener.onZoom(this);
-            }
-
-            if (updatePrev) {
-                mPrevSpanX = mCurrSpanX;
-                mPrevSpanY = mCurrSpanY;
-                mPrevSpan = mCurrSpan;
-                mPrevTime = mCurrTime;
-            }
-        }
-
-        return true;
-    }
-
-    private boolean inAnchoredZoomMode() {
-        return mAnchoredZoomMode != ANCHORED_ZOOM_MODE_NONE;
-    }
-
-    /**
-     * Set whether the associated {@link OnZoomGestureListener} should receive onZoom callbacks
-     * when the user performs a doubleTap followed by a swipe.
-     *
-     * <p>If not set, this is enabled by default.
-     *
-     * @param enabled {@code true} to enable quick zooming, {@code false} to disable.
-     */
-    public void setQuickZoomEnabled(boolean enabled) {
-        mQuickZoomEnabled = enabled;
-        if (mQuickZoomEnabled && mGestureDetector == null) {
-            GestureDetector.SimpleOnGestureListener gestureListener =
-                    new GestureDetector.SimpleOnGestureListener() {
-                        @Override
-                        public boolean onDoubleTap(MotionEvent e) {
-                            // Double tap: start watching for a swipe
-                            mAnchoredZoomStartX = e.getX();
-                            mAnchoredZoomStartY = e.getY();
-                            mAnchoredZoomMode = ANCHORED_ZOOM_MODE_DOUBLE_TAP;
-                            return true;
-                        }
-                    };
-            mGestureDetector = new GestureDetector(mContext, gestureListener, mHandler);
-        }
-    }
-
-    /**
-     * Return whether the quick zoom gesture, in which the user performs a double tap followed by a
-     * swipe, should perform zooming.
-     *
-     * @see #setQuickZoomEnabled(boolean)
-     */
-    public boolean isQuickZoomEnabled() {
-        return mQuickZoomEnabled;
-    }
-
-    /**
-     * Sets whether the associates {@link OnZoomGestureListener} should receive
-     * onZoom callbacks when the user uses a stylus and presses the button.
-     *
-     * <p>If not set, this is enabled by default.
-     *
-     * @param enabled {@code true} to enable stylus zooming, {@code false} to disable.
-     */
-    public void setStylusZoomEnabled(boolean enabled) {
-        mStylusZoomEnabled = enabled;
-    }
-
-    /**
-     * Return whether the stylus zoom gesture, in which the user uses a stylus and presses the
-     * button, should perform zooming. {@see #setStylusScaleEnabled(boolean)}
-     */
-    public boolean isStylusZoomEnabled() {
-        return mStylusZoomEnabled;
-    }
-
-    /**
-     * Returns {@code true} if a zoom gesture is in progress.
-     */
-    public boolean isInProgress() {
-        return mInProgress;
-    }
-
-    /**
-     * Get the X coordinate of the current gesture's focal point.
-     * If a gesture is in progress, the focal point is between
-     * each of the pointers forming the gesture.
-     *
-     * <p>If {@link #isInProgress()} would return false, the result of this
-     * function is undefined.
-     *
-     * @return X coordinate of the focal point in pixels.
-     */
-    public float getFocusX() {
-        return mFocusX;
-    }
-
-    /**
-     * Get the Y coordinate of the current gesture's focal point.
-     * If a gesture is in progress, the focal point is between
-     * each of the pointers forming the gesture.
-     *
-     * <p>If {@link #isInProgress()} would return false, the result of this
-     * function is undefined.
-     *
-     * @return Y coordinate of the focal point in pixels.
-     */
-    public float getFocusY() {
-        return mFocusY;
-    }
-
-    /**
-     * Return the average distance between each of the pointers forming the
-     * gesture in progress through the focal point.
-     *
-     * @return Distance between pointers in pixels.
-     */
-    public float getCurrentSpan() {
-        return mCurrSpan;
-    }
-
-    /**
-     * Return the average X distance between each of the pointers forming the
-     * gesture in progress through the focal point.
-     *
-     * @return Distance between pointers in pixels.
-     */
-    public float getCurrentSpanX() {
-        return mCurrSpanX;
-    }
-
-    /**
-     * Return the average Y distance between each of the pointers forming the
-     * gesture in progress through the focal point.
-     *
-     * @return Distance between pointers in pixels.
-     */
-    public float getCurrentSpanY() {
-        return mCurrSpanY;
-    }
-
-    /**
-     * Return the previous average distance between each of the pointers forming the
-     * gesture in progress through the focal point.
-     *
-     * @return Previous distance between pointers in pixels.
-     */
-    public float getPreviousSpan() {
-        return mPrevSpan;
-    }
-
-    /**
-     * Return the previous average X distance between each of the pointers forming the
-     * gesture in progress through the focal point.
-     *
-     * @return Previous distance between pointers in pixels.
-     */
-    public float getPreviousSpanX() {
-        return mPrevSpanX;
-    }
-
-    /**
-     * Return the previous average Y distance between each of the pointers forming the
-     * gesture in progress through the focal point.
-     *
-     * @return Previous distance between pointers in pixels.
-     */
-    public float getPreviousSpanY() {
-        return mPrevSpanY;
-    }
-
-    /**
-     * Return the scaling factor from the previous zoom event to the current
-     * event. This value is defined as
-     * ({@link #getCurrentSpan()} / {@link #getPreviousSpan()}).
-     *
-     * @return The current scaling factor.
-     */
-    public float getScaleFactor() {
-        if (inAnchoredZoomMode()) {
-            // Drag is moving up; the further away from the gesture
-            // start, the smaller the span should be, the closer,
-            // the larger the span, and therefore the larger the scale
-            final boolean scaleUp =
-                    (mEventBeforeOrAboveStartingGestureEvent
-                            && (mCurrSpan < mPrevSpan))
-                            || (!mEventBeforeOrAboveStartingGestureEvent
-                            && (mCurrSpan > mPrevSpan));
-            final float spanDiff = (Math.abs(1 - (mCurrSpan / mPrevSpan)) * SCALE_FACTOR);
-            return mPrevSpan <= mSpanSlop ? 1 : scaleUp ? (1 + spanDiff) : (1 - spanDiff);
-        }
-        return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1;
-    }
-
-    /**
-     * Return the time difference in milliseconds between the previous
-     * accepted zooming event and the current zooming event.
-     *
-     * @return Time difference since the last zooming event in milliseconds.
-     */
-    public long getTimeDelta() {
-        return mCurrTime - mPrevTime;
-    }
-
-    /**
-     * Return the event time of the current event being processed.
-     *
-     * @return Current event time in milliseconds.
-     */
-    public long getEventTime() {
-        return mCurrTime;
-    }
-}
diff --git a/camera/camera-viewfinder-core/src/main/java/androidx/viewfinder/core/ZoomGestureDetector.kt b/camera/camera-viewfinder-core/src/main/java/androidx/viewfinder/core/ZoomGestureDetector.kt
new file mode 100644
index 0000000..195b4cb
--- /dev/null
+++ b/camera/camera-viewfinder-core/src/main/java/androidx/viewfinder/core/ZoomGestureDetector.kt
@@ -0,0 +1,433 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.viewfinder.core
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.view.GestureDetector
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewConfiguration
+import androidx.annotation.RequiresApi
+import androidx.annotation.RestrictTo
+import androidx.viewfinder.core.ZoomGestureDetector.OnZoomGestureListener
+import kotlin.math.abs
+import kotlin.math.hypot
+
+/**
+ * Detects scaling transformation gestures that interprets zooming events using the supplied
+ * [MotionEvent]s.
+ *
+ * The [OnZoomGestureListener] callback will notify users when a particular
+ * gesture event has occurred.
+ *
+ * This class should only be used with [MotionEvent]s reported via touch.
+ *
+ * To use this class:
+ * - Create an instance of the `ZoomGestureDetector` for your [View]
+ * - In the [View.onTouchEvent] method ensure you call [onTouchEvent]. The methods defined in your
+ * callback will be executed when the events occur.
+ */
+// TODO(b/314701735): update the documentation with examples using camera classes.
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+class ZoomGestureDetector @SuppressLint("ExecutorRegistration") constructor(
+    private val context: Context,
+    private val spanSlop: Int = ViewConfiguration.get(context).scaledTouchSlop * 2,
+    private val minSpan: Int = DEFAULT_MIN_SPAN,
+    private val listener: OnZoomGestureListener
+) {
+    /**
+     * The listener for receiving notifications when gestures occur.
+     *
+     * An application will receive events in the following order:
+     * - One [ZOOM_GESTURE_BEGIN]
+     * - Zero or more [ZOOM_GESTURE_MOVE]
+     * - One [ZOOM_GESTURE_END]
+     */
+    fun interface OnZoomGestureListener {
+        /**
+         * Responds to the events of a zooming gesture.
+         *
+         * Return `true` to indicate the event is handled by the listener.
+         * - For [ZOOM_GESTURE_MOVE] events, the detector will continue to accumulate movement if
+         * it's not handled. This can be useful if an application, for example, only wants to update
+         * scaling factors if the change is greater than `0.01`.
+         * - For [ZOOM_GESTURE_BEGIN] events, the detector will ignore the rest of the gesture if
+         * it's not handled. For example, if a gesture is beginning with a focal point outside of a
+         * region where it makes sense, [ZOOM_GESTURE_BEGIN] event may return `false` to ignore the
+         * rest of the gesture.
+         * - For [ZOOM_GESTURE_END] events, the return value is ignored and the zoom gesture will
+         * end regardless of what is returned.
+         *
+         * Once receiving [ZOOM_GESTURE_END] event, [focusX] and [focusY] will return focal point of
+         * the pointers remaining on the screen.
+         *
+         * @type The type of the event. Possible values include [ZOOM_GESTURE_MOVE],
+         * [ZOOM_GESTURE_BEGIN] and [ZOOM_GESTURE_END].
+         * @param detector The detector reporting the event - use this to retrieve extended info
+         * about event state.
+         * @return Whether or not the detector should consider this event as handled.
+         */
+        fun onZoom(type: Int, detector: ZoomGestureDetector): Boolean
+    }
+
+    /**
+     * The X coordinate of the current gesture's focal point in pixels. If a gesture is in progress,
+     * the focal point is between each of the pointers forming the gesture.
+     *
+     * If [isInProgress] would return `false`, the result of this function is undefined.
+     */
+    var focusX = 0f
+        private set
+
+    /**
+     * The Y coordinate of the current gesture's focal point in pixels. If a gesture is in progress,
+     * the focal point is between each of the pointers forming the gesture.
+     *
+     * If [isInProgress] would return `false`, the result of this function is undefined.
+     */
+    var focusY = 0f
+        private set
+
+    /**
+     * Whether the quick zoom gesture, in which the user performs a double tap followed by a swipe,
+     * should perform zooming.
+     *
+     * If not set, this is enabled by default.
+     */
+    var isQuickZoomEnabled: Boolean = true
+        set(enabled) {
+            field = enabled
+            if (field && gestureDetector == null) {
+                val gestureListener: GestureDetector.SimpleOnGestureListener =
+                    object : GestureDetector.SimpleOnGestureListener() {
+                        override fun onDoubleTap(e: MotionEvent): Boolean {
+                            // Double tap: start watching for a swipe
+                            anchoredZoomStartX = e.x
+                            anchoredZoomStartY = e.y
+                            anchoredZoomMode = ANCHORED_ZOOM_MODE_DOUBLE_TAP
+                            return true
+                        }
+                    }
+                gestureDetector = GestureDetector(context, gestureListener)
+            }
+        }
+
+    /**
+     * Whether the stylus zoom gesture, in which the user uses a stylus and presses the button,
+     * should perform zooming.
+     *
+     * If not set, this is enabled by default.
+     */
+    var isStylusZoomEnabled = true
+
+    /**
+     * The average distance in pixels between each of the pointers forming the gesture in progress
+     * through the focal point.
+     */
+    var currentSpan = 0f
+        private set
+
+    /**
+     * The previous average distance in pixels between each of the pointers forming the gesture in
+     * progress through the focal point.
+     */
+    var previousSpan = 0f
+        private set
+
+    /**
+     * The average X distance in pixels between each of the pointers forming the gesture in progress
+     * through the focal point.
+     */
+    var currentSpanX = 0f
+        private set
+
+    /**
+     * The average Y distance in pixels between each of the pointers forming the gesture in progress
+     * through the focal point.
+     */
+    var currentSpanY = 0f
+        private set
+
+    /**
+     * The previous average X distance in pixels between each of the pointers forming the gesture in
+     * progress through the focal point.
+     */
+    var previousSpanX = 0f
+        private set
+
+    /**
+     * The previous average Y distance in pixels between each of the pointers forming the gesture in
+     * progress through the focal point.
+     */
+    var previousSpanY = 0f
+        private set
+
+    /**
+     * The event time in milliseconds of the current event being processed.
+     */
+    var eventTime: Long = 0
+        private set
+
+    /**
+     * Whether a zoom gesture is in progress.
+     */
+    var isInProgress = false
+        private set
+
+    private var initialSpan = 0f
+    private var prevTime: Long = 0
+    private var anchoredZoomStartX = 0f
+    private var anchoredZoomStartY = 0f
+    private var anchoredZoomMode = ANCHORED_ZOOM_MODE_NONE
+    private var gestureDetector: GestureDetector? = null
+    private var eventBeforeOrAboveStartingGestureEvent = false
+
+    /**
+     * Accepts [MotionEvent]s and dispatches events to a [OnZoomGestureListener] when appropriate.
+     *
+     * Applications should pass a complete and consistent event stream to this method.
+     *
+     * A complete and consistent event stream involves all [MotionEvent]s from the initial
+     * [MotionEvent.ACTION_DOWN] to the final [MotionEvent.ACTION_UP] or
+     * [MotionEvent.ACTION_CANCEL].
+     *
+     * @param event The event to process.
+     * @return `true` if the event was processed and the detector wants to receive the
+     * rest of the MotionEvents in this event stream.
+     */
+    fun onTouchEvent(event: MotionEvent): Boolean {
+        eventTime = event.eventTime
+
+        val action = event.actionMasked
+
+        // Forward the event to check for double tap gesture
+        if (isQuickZoomEnabled) {
+            gestureDetector!!.onTouchEvent(event)
+        }
+
+        val count = event.pointerCount
+        val isStylusButtonDown = (event.buttonState and MotionEvent.BUTTON_STYLUS_PRIMARY) != 0
+
+        val anchoredZoomCancelled =
+            anchoredZoomMode == ANCHORED_ZOOM_MODE_STYLUS && !isStylusButtonDown
+        val streamComplete = action == MotionEvent.ACTION_UP ||
+            action == MotionEvent.ACTION_CANCEL ||
+            anchoredZoomCancelled
+
+        if (action == MotionEvent.ACTION_DOWN || streamComplete) {
+            // Reset any scale in progress with the listener.
+            // If it's an ACTION_DOWN we're beginning a new event stream.
+            // This means the app probably didn't give us all the events. Shame on it.
+            if (isInProgress) {
+                listener.onZoom(ZOOM_GESTURE_END, this)
+                isInProgress = false
+                initialSpan = 0f
+                anchoredZoomMode = ANCHORED_ZOOM_MODE_NONE
+            } else if (inAnchoredZoomMode() && streamComplete) {
+                isInProgress = false
+                initialSpan = 0f
+                anchoredZoomMode = ANCHORED_ZOOM_MODE_NONE
+            }
+            if (streamComplete) {
+                return true
+            }
+        }
+
+        if (!isInProgress &&
+            isStylusZoomEnabled &&
+            !inAnchoredZoomMode() &&
+            !streamComplete &&
+            isStylusButtonDown) {
+            // Start of a button zoom gesture
+            anchoredZoomStartX = event.x
+            anchoredZoomStartY = event.y
+            anchoredZoomMode = ANCHORED_ZOOM_MODE_STYLUS
+            initialSpan = 0f
+        }
+
+        val configChanged = action == MotionEvent.ACTION_DOWN ||
+            action == MotionEvent.ACTION_POINTER_UP ||
+            action == MotionEvent.ACTION_POINTER_DOWN ||
+            anchoredZoomCancelled
+
+        val pointerUp = action == MotionEvent.ACTION_POINTER_UP
+        val skipIndex = if (pointerUp) event.actionIndex else -1
+
+        // Determine focal point
+        var sumX = 0f
+        var sumY = 0f
+        val div = if (pointerUp) count - 1 else count
+        val focusX: Float
+        val focusY: Float
+        if (inAnchoredZoomMode()) {
+            // In anchored scale mode, the focal pt is always where the double tap
+            // or button down gesture started
+            focusX = anchoredZoomStartX
+            focusY = anchoredZoomStartY
+            eventBeforeOrAboveStartingGestureEvent = if (event.y < focusY) {
+                true
+            } else {
+                false
+            }
+        } else {
+            for (i in 0 until count) {
+                if (skipIndex == i) continue
+                sumX += event.getX(i)
+                sumY += event.getY(i)
+            }
+            focusX = sumX / div
+            focusY = sumY / div
+        }
+
+        // Determine average deviation from focal point
+        var devSumX = 0f
+        var devSumY = 0f
+        for (i in 0 until count) {
+            if (skipIndex == i) continue
+
+            // Convert the resulting diameter into a radius.
+            devSumX += abs((event.getX(i) - focusX))
+            devSumY += abs((event.getY(i) - focusY))
+        }
+        val devX = devSumX / div
+        val devY = devSumY / div
+
+        // Span is the average distance between touch points through the focal point;
+        // i.e. the diameter of the circle with a radius of the average deviation from
+        // the focal point.
+        val spanX = devX * 2
+        val spanY = devY * 2
+        val span: Float = if (inAnchoredZoomMode()) {
+            spanY
+        } else {
+            hypot(spanX, spanY)
+        }
+
+        // Dispatch begin/end events as needed.
+        // If the configuration changes, notify the app to reset its current state by beginning
+        // a fresh zoom event stream.
+        val wasInProgress = isInProgress
+        this.focusX = focusX
+        this.focusY = focusY
+        if (!inAnchoredZoomMode() && isInProgress && (span < minSpan || configChanged)) {
+            listener.onZoom(ZOOM_GESTURE_END, this)
+            isInProgress = false
+            initialSpan = span
+        }
+        if (configChanged) {
+            currentSpanX = spanX
+            previousSpanX = currentSpanX
+            currentSpanY = spanY
+            previousSpanY = currentSpanY
+            currentSpan = span
+            previousSpan = currentSpan
+            initialSpan = previousSpan
+        }
+        val minSpan = if (inAnchoredZoomMode()) spanSlop else minSpan
+        if (!isInProgress &&
+            span >= minSpan &&
+            (wasInProgress || abs((span - initialSpan)) > spanSlop)) {
+            currentSpanX = spanX
+            previousSpanX = currentSpanX
+            currentSpanY = spanY
+            previousSpanY = currentSpanY
+            currentSpan = span
+            previousSpan = currentSpan
+            prevTime = eventTime
+            isInProgress = listener.onZoom(ZOOM_GESTURE_BEGIN, this)
+        }
+
+        // Handle motion; focal point and span/scale factor are changing.
+        if (action == MotionEvent.ACTION_MOVE) {
+            currentSpanX = spanX
+            currentSpanY = spanY
+            currentSpan = span
+
+            var updatePrev = true
+
+            if (isInProgress) {
+                updatePrev = listener.onZoom(ZOOM_GESTURE_MOVE, this)
+            }
+
+            if (updatePrev) {
+                previousSpanX = currentSpanX
+                previousSpanY = currentSpanY
+                previousSpan = currentSpan
+                prevTime = eventTime
+            }
+        }
+        return true
+    }
+
+    private fun inAnchoredZoomMode(): Boolean {
+        return anchoredZoomMode != ANCHORED_ZOOM_MODE_NONE
+    }
+
+    val scaleFactor: Float
+        /**
+         * Returns the scaling factor from the previous zoom event to the current event. This value
+         * is defined as ([currentSpan] / [previousSpan]).
+         *
+         * @return The current scaling factor.
+         */
+        get() {
+            if (inAnchoredZoomMode()) {
+                // Drag is moving up; the further away from the gesture start, the smaller the span
+                // should be, the closer, the larger the span, and therefore the larger the scale
+                val scaleUp = eventBeforeOrAboveStartingGestureEvent &&
+                    currentSpan < previousSpan ||
+                    !eventBeforeOrAboveStartingGestureEvent &&
+                    currentSpan > previousSpan
+                val spanDiff = (abs((1 - currentSpan / previousSpan)) * SCALE_FACTOR)
+                return if (previousSpan <= spanSlop) 1.0f
+                else if (scaleUp) 1.0f + spanDiff
+                else 1.0f - spanDiff
+            }
+            return if (previousSpan > 0) currentSpan / previousSpan else 1.0f
+        }
+
+    val timeDelta: Long
+        /**
+         * Returns the time difference in milliseconds between the previous accepted zooming event
+         * and the current zooming event.
+         *
+         * @return Time difference since the last zooming event in milliseconds.
+         */
+        get() = eventTime - prevTime
+
+    companion object {
+        private const val TAG = "ZoomGestureDetector"
+
+        /** The moving events of a gesture in progress. Reported by pointer motion. */
+        const val ZOOM_GESTURE_MOVE = 0
+        /** The beginning of a zoom gesture. Reported by new pointers going down. */
+        const val ZOOM_GESTURE_BEGIN = 1
+        /** The end of a zoom gesture. Reported by existing pointers going up. */
+        const val ZOOM_GESTURE_END = 2
+
+        // The default minimum span that the detector interprets a zooming event with. It's set to 0
+        // to give the most responsiveness.
+        // TODO(b/314702145): define a different span if appropriate.
+        private const val DEFAULT_MIN_SPAN = 0
+        private const val SCALE_FACTOR = .5f
+        private const val ANCHORED_ZOOM_MODE_NONE = 0
+        private const val ANCHORED_ZOOM_MODE_DOUBLE_TAP = 1
+        private const val ANCHORED_ZOOM_MODE_STYLUS = 2
+    }
+}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/FovDeviceTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/FovDeviceTest.kt
new file mode 100644
index 0000000..3d160b0
--- /dev/null
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/FovDeviceTest.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.integration.core
+
+import android.content.Context
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.Camera2Config
+import androidx.camera.camera2.pipe.integration.CameraPipeConfig
+import androidx.camera.core.CameraInfo
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.CameraXConfig
+import androidx.camera.core.impl.CameraInfoInternal
+import androidx.camera.core.internal.CameraUseCaseAdapter
+import androidx.camera.testing.impl.CameraPipeConfigTestRule
+import androidx.camera.testing.impl.CameraUtil
+import androidx.camera.testing.impl.CameraXUtil
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth
+import java.util.Collections
+import java.util.concurrent.TimeUnit
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+private val DEFAULT_CAMERA_ID_GROUP = Collections.unmodifiableSet(setOf("0", "1"))
+
+@LargeTest
+@RunWith(Parameterized::class)
+@SdkSuppress(minSdkVersion = 21)
+class FovDeviceTest(
+    private val cameraId: String,
+    private val implName: String,
+    private val cameraXConfig: CameraXConfig
+) {
+    @get:Rule
+    val cameraPipeConfigTestRule = CameraPipeConfigTestRule(
+        active = implName == CameraPipeConfig::class.simpleName,
+    )
+
+    @get:Rule
+    val cameraRule = CameraUtil.grantCameraPermissionAndPreTest(
+        CameraUtil.PreTestCameraIdList(cameraXConfig)
+    )
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "cameraId: {0}, implName: {1}")
+        fun data(): List<Array<Any?>> {
+            val paramList = mutableListOf<Array<Any?>>()
+            CameraUtil.getBackwardCompatibleCameraIdListOrThrow().forEach { cameraId ->
+                paramList.add(
+                    arrayOf(
+                        cameraId,
+                        Camera2Config::class.simpleName,
+                        Camera2Config.defaultConfig()
+                    )
+                )
+                paramList.add(
+                    arrayOf(
+                        cameraId,
+                        CameraPipeConfig::class.simpleName,
+                        CameraPipeConfig.defaultConfig()
+                    )
+                )
+            }
+            return paramList
+        }
+    }
+
+    private lateinit var cameraUseCaseAdapter: CameraUseCaseAdapter
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Before
+    fun setUp() {
+        CameraXUtil.initialize(
+            context,
+            cameraXConfig
+        ).get()
+
+        val cameraSelector = CameraSelector.Builder().addCameraFilter { cameraInfoList ->
+            val filteredList = ArrayList<CameraInfo>()
+            cameraInfoList.forEach { cameraInfo ->
+                if ((cameraInfo as CameraInfoInternal).cameraId == cameraId) {
+                    filteredList.add(cameraInfo)
+                }
+            }
+            filteredList
+        }.build()
+        cameraUseCaseAdapter =
+            CameraUtil.createCameraUseCaseAdapter(context, cameraSelector)
+    }
+
+    @After
+    fun tearDown() {
+        CameraXUtil.shutdown()[10000, TimeUnit.MILLISECONDS]
+    }
+
+    @RequiresApi(Build.VERSION_CODES.R)
+    @Test
+    fun intrinsicZoomRatio_greaterThanZero() {
+        Truth.assertThat(cameraUseCaseAdapter.cameraInfo.intrinsicZoomRatio).isGreaterThan(0)
+    }
+
+    @Test
+    fun intrinsicZoomRatio_defaultToOne() {
+        Assume.assumeTrue(DEFAULT_CAMERA_ID_GROUP.contains(cameraId))
+        Truth.assertThat(cameraUseCaseAdapter.cameraInfo.intrinsicZoomRatio).isEqualTo(1.0F)
+    }
+}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/OpenCloseCaptureSessionStressTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/OpenCloseCaptureSessionStressTest.kt
index cb494fb..39430c27 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/OpenCloseCaptureSessionStressTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/OpenCloseCaptureSessionStressTest.kt
@@ -38,6 +38,7 @@
 import androidx.camera.testing.impl.LabTestRule
 import androidx.camera.testing.impl.StressTestRule
 import androidx.camera.testing.impl.SurfaceTextureProvider
+import androidx.camera.testing.impl.WakelockEmptyActivityRule
 import androidx.camera.testing.impl.fakes.FakeLifecycleOwner
 import androidx.camera.video.Recorder
 import androidx.camera.video.VideoCapture
@@ -84,6 +85,9 @@
     @get:Rule
     val repeatRule = RepeatRule()
 
+    @get:Rule
+    val wakelockEmptyActivityRule = WakelockEmptyActivityRule()
+
     private val context = ApplicationProvider.getApplicationContext<Context>()
 
     private lateinit var cameraProvider: ProcessCameraProvider
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/camera2/PreviewTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/camera2/PreviewTest.kt
index e639565..6a2edcb 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/camera2/PreviewTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/camera2/PreviewTest.kt
@@ -47,6 +47,7 @@
 import androidx.camera.testing.impl.GLUtil
 import androidx.camera.testing.impl.SurfaceTextureProvider
 import androidx.camera.testing.impl.SurfaceTextureProvider.SurfaceTextureCallback
+import androidx.camera.testing.impl.WakelockEmptyActivityRule
 import androidx.core.util.Consumer
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.filters.LargeTest
@@ -90,6 +91,9 @@
         PreTestCameraIdList(cameraConfig)
     )
 
+    @get:Rule
+    val wakelockEmptyActivityRule = WakelockEmptyActivityRule()
+
     companion object {
         private const val ANY_THREAD_NAME = "any-thread-name"
         private val DEFAULT_RESOLUTION: Size by lazy { Size(640, 480) }
diff --git a/camera/integration-tests/extensionstestapp/build.gradle b/camera/integration-tests/extensionstestapp/build.gradle
index b96295f..7e35cf7 100644
--- a/camera/integration-tests/extensionstestapp/build.gradle
+++ b/camera/integration-tests/extensionstestapp/build.gradle
@@ -64,7 +64,6 @@
     // Guava
     implementation(libs.guavaAndroid)
     implementation("androidx.viewpager2:viewpager2:1.0.0")
-    implementation(project(':camera:camera-camera2-pipe-integration'))
 
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testCore)
@@ -78,7 +77,6 @@
         exclude(group:"androidx.test")
     }
     androidTestImplementation(project(":internal-testutils-runtime"))
-    androidTestImplementation(project(":concurrent:concurrent-futures"))
     androidTestCompileOnly(project(":camera:camera-extensions-stub"))
 
     // Testing resource dependency for manifest
diff --git a/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/CameraExtensionsActivity.java b/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/CameraExtensionsActivity.java
index 859569e..2a452c7 100644
--- a/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/CameraExtensionsActivity.java
+++ b/camera/integration-tests/extensionstestapp/src/main/java/androidx/camera/integration/extensions/CameraExtensionsActivity.java
@@ -73,7 +73,6 @@
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.camera.camera2.interop.Camera2Interop;
 import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
-import androidx.camera.camera2.pipe.integration.CameraPipeConfig;
 import androidx.camera.core.Camera;
 import androidx.camera.core.CameraControl;
 import androidx.camera.core.CameraInfo;
@@ -93,7 +92,6 @@
 import androidx.camera.integration.extensions.utils.ExtensionModeUtil;
 import androidx.camera.integration.extensions.utils.FpsRecorder;
 import androidx.camera.integration.extensions.validation.CameraValidationResultActivity;
-import androidx.camera.lifecycle.ExperimentalCameraProviderConfiguration;
 import androidx.camera.lifecycle.ProcessCameraProvider;
 import androidx.camera.video.MediaStoreOutputOptions;
 import androidx.camera.video.PendingRecording;
@@ -132,8 +130,6 @@
 
     private static final String TAG = "CameraExtensionActivity";
     private static final int PERMISSIONS_REQUEST_CODE = 42;
-    public static final String INTENT_EXTRA_CAMERA_IMPLEMENTATION = "camera_implementation";
-    public static final String CAMERA_PIPE_IMPLEMENTATION_OPTION = "camera_pipe";
 
     private CameraSelector mCurrentCameraSelector = CameraSelector.DEFAULT_BACK_CAMERA;
 
@@ -602,10 +598,7 @@
         mPreviewView = (PreviewView) viewFinderStub.inflate();
         mPreviewView.setImplementationMode(PreviewView.ImplementationMode.COMPATIBLE);
         setupPinchToZoomAndTapToFocus(mPreviewView);
-        String cameraImplementation =
-                getIntent().getStringExtra(INTENT_EXTRA_CAMERA_IMPLEMENTATION);
         Futures.addCallback(setupPermissions(), new FutureCallback<Boolean>() {
-            @OptIn(markerClass = ExperimentalCameraProviderConfiguration.class)
             @Override
             public void onSuccess(@Nullable Boolean result) {
                 mPermissionsGranted = Preconditions.checkNotNull(result);
@@ -618,10 +611,6 @@
                     return;
                 }
 
-                if (cameraImplementation != null &&
-                        cameraImplementation.equals(CAMERA_PIPE_IMPLEMENTATION_OPTION)) {
-                    ProcessCameraProvider.configureInstance(CameraPipeConfig.defaultConfig());
-                }
                 ListenableFuture<ProcessCameraProvider> cameraProviderFuture =
                         ProcessCameraProvider.getInstance(CameraExtensionsActivity.this);
 
diff --git a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageAnalysisOrientationConfigChangesTest.kt b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageAnalysisOrientationConfigChangesTest.kt
index f1ca9eb..a14034a 100644
--- a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageAnalysisOrientationConfigChangesTest.kt
+++ b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageAnalysisOrientationConfigChangesTest.kt
@@ -69,7 +69,10 @@
             "Known issue on this device. Please see b/198744779",
             listOf(
                 "redmi note 9s",
-                "redmi note 8"
+                "redmi note 8",
+                "m2003j15sc", // Redmi Note 9
+                "m2006c3lg", // Redmi 9A
+                "m2006c3mg" // Redmi 9C
             ).contains(Build.MODEL.lowercase(Locale.US)) && rotation == Surface.ROTATION_180
         )
         setUp(lensFacing)
diff --git a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageCaptureOrientationConfigChangesTest.kt b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageCaptureOrientationConfigChangesTest.kt
index a4f4bf4..32c1c7e 100644
--- a/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageCaptureOrientationConfigChangesTest.kt
+++ b/camera/integration-tests/uiwidgetstestapp/src/androidTest/java/androidx/camera/integration/uiwidgets/rotations/ImageCaptureOrientationConfigChangesTest.kt
@@ -74,7 +74,10 @@
             "Known issue on this device. Please see b/198744779",
             listOf(
                 "redmi note 9s",
-                "redmi note 8"
+                "redmi note 8",
+                "m2003j15sc", // Redmi Note 9
+                "m2006c3lg", // Redmi 9A
+                "m2006c3mg" // Redmi 9C
             ).contains(Build.MODEL.lowercase(Locale.US)) && rotation == Surface.ROTATION_180
         )
         CoreAppTestUtil.assumeCompatibleDevice()
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/ShowcaseSession.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/ShowcaseSession.java
index 5dd4719..4dc3cc8 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/ShowcaseSession.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/ShowcaseSession.java
@@ -33,9 +33,9 @@
 import androidx.car.app.sample.showcase.common.renderer.Renderer;
 import androidx.car.app.sample.showcase.common.renderer.SurfaceController;
 import androidx.car.app.sample.showcase.common.screens.ResultDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.navigationdemos.NavigatingDemoScreen;
 import androidx.car.app.sample.showcase.common.screens.navigationdemos.NavigationNotificationService;
 import androidx.car.app.sample.showcase.common.screens.navigationdemos.NavigationNotificationsDemoScreen;
-import androidx.car.app.sample.showcase.common.screens.navigationdemos.navigationtemplates.NavigatingDemoScreen;
 import androidx.car.app.sample.showcase.common.screens.userinteractions.RequestPermissionScreen;
 import androidx.lifecycle.DefaultLifecycleObserver;
 import androidx.lifecycle.Lifecycle;
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/StartScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/StartScreen.java
index 3d7465b..7f9c9f9 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/StartScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/StartScreen.java
@@ -25,6 +25,7 @@
 import androidx.car.app.model.ListTemplate;
 import androidx.car.app.model.Row;
 import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.screens.MapDemosScreen;
 import androidx.car.app.sample.showcase.common.screens.NavigationDemosScreen;
 import androidx.car.app.sample.showcase.common.screens.SettingsScreen;
 import androidx.car.app.sample.showcase.common.screens.TemplateLayoutsDemoScreen;
@@ -52,6 +53,10 @@
         listBuilder.addItem(createRowForScreen(R.string.user_interactions_demo_title,
                 new UserInteractionsDemoScreen(1, getCarContext())));
 
+        listBuilder.addItem(createRowForScreen(R.string.map_demos_title,
+                createCarIconForImage(R.drawable.ic_place_white_24dp),
+                new MapDemosScreen(getCarContext())));
+
         listBuilder.addItem(createRowForScreen(R.string.nav_demos_title,
                 createCarIconForImage(R.drawable.ic_map_white_48dp),
                 NavigationDemosScreen.createScreen(getCarContext())));
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/MapDemosScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/MapDemosScreen.java
new file mode 100644
index 0000000..7a96c58
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/MapDemosScreen.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.app.sample.showcase.common.screens;
+
+import static androidx.car.app.model.Action.BACK;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.Screen;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.ListTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.screens.mapdemos.MapWithContentDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.mapdemos.PlaceListNavigationTemplateDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.mapdemos.PlaceListTemplateBrowseDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.mapdemos.RoutePreviewDemoScreen;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** A screen demonstrating different template layouts. */
+public final class MapDemosScreen extends Screen {
+
+    public MapDemosScreen(@NonNull CarContext carContext) {
+        super(carContext);
+    }
+
+    @NonNull
+    @Override
+    public Template onGetTemplate() {
+        List<Row> screenList = new ArrayList<>();
+        screenList.add(buildBrowsableRow(new MapWithContentDemoScreen(getCarContext()),
+                R.string.map_with_content_demo_title));
+        screenList.add(buildRowForTemplate(new PlaceListNavigationTemplateDemoScreen(
+                getCarContext()),
+                R.string.place_list_nav_template_demo_title));
+        screenList.add(buildRowForTemplate(new RoutePreviewDemoScreen(getCarContext()),
+                R.string.route_preview_template_demo_title));
+        screenList.add(buildRowForTemplate(new PlaceListTemplateBrowseDemoScreen(getCarContext()),
+                R.string.place_list_template_demo_title));
+
+        ItemList.Builder listBuilder = new ItemList.Builder();
+
+        for (int i = 0; i < screenList.size(); i++) {
+            listBuilder.addItem(screenList.get(i));
+        }
+
+        return new ListTemplate.Builder()
+                .setSingleList(listBuilder.build())
+                .setTitle(getCarContext().getString(R.string.map_demos_title))
+                .setHeaderAction(BACK)
+                .build();
+    }
+
+    private Row buildRowForTemplate(Screen screen, int title) {
+        return new Row.Builder()
+                .setTitle(getCarContext().getString(title))
+                .setOnClickListener(() -> getScreenManager().push(screen))
+                .build();
+    }
+
+    private Row buildBrowsableRow(Screen screen, int title) {
+        return new Row.Builder()
+                .setTitle(getCarContext().getString(title))
+                .setOnClickListener(() -> getScreenManager().push(screen))
+                .setBrowsable(true)
+                .build();
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/NavigationDemosScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/NavigationDemosScreen.java
index f9de967..032dba6 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/NavigationDemosScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/NavigationDemosScreen.java
@@ -20,21 +20,15 @@
 import androidx.car.app.CarContext;
 import androidx.car.app.Screen;
 import androidx.car.app.ScreenManager;
-import androidx.car.app.model.CarIcon;
 import androidx.car.app.model.Row;
 import androidx.car.app.sample.showcase.common.R;
-import androidx.car.app.sample.showcase.common.screens.navigationdemos.MapTemplateWithListDemoScreen;
-import androidx.car.app.sample.showcase.common.screens.navigationdemos.MapTemplateWithPaneDemoScreen;
-import androidx.car.app.sample.showcase.common.screens.navigationdemos.MapTemplateWithToggleDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.navigationdemos.ArrivedDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.navigationdemos.JunctionImageDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.navigationdemos.LoadingDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.navigationdemos.NavigatingDemoScreen;
 import androidx.car.app.sample.showcase.common.screens.navigationdemos.NavigationMapOnlyScreen;
 import androidx.car.app.sample.showcase.common.screens.navigationdemos.NavigationNotificationsDemoScreen;
-import androidx.car.app.sample.showcase.common.screens.navigationdemos.NavigationTemplateDemoScreen;
-import androidx.car.app.sample.showcase.common.screens.navigationdemos.PlaceListNavigationTemplateDemoScreen;
-import androidx.car.app.sample.showcase.common.screens.navigationdemos.PlaceListTemplateBrowseDemoScreen;
-import androidx.car.app.sample.showcase.common.screens.navigationdemos.RoutePreviewDemoScreen;
 import androidx.car.app.sample.showcase.common.screens.paging.PagedListTemplate;
-import androidx.car.app.versioning.CarAppApiLevels;
-import androidx.core.graphics.drawable.IconCompat;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -60,21 +54,26 @@
 
         screenList.add(createRow(
                 screenManager,
-                buildCarIcon(R.drawable.ic_explore_white_24dp),
-                mCarContext.getString(R.string.nav_template_demos_title),
-                new NavigationTemplateDemoScreen(mCarContext)
+                mCarContext.getString(R.string.loading_demo_title),
+                new LoadingDemoScreen(mCarContext)
         ));
 
         screenList.add(createRow(
                 screenManager,
-                mCarContext.getString(R.string.place_list_nav_template_demo_title),
-                new PlaceListNavigationTemplateDemoScreen(mCarContext)
+                mCarContext.getString(R.string.arrived_demo_title),
+                new ArrivedDemoScreen(mCarContext)
         ));
 
         screenList.add(createRow(
                 screenManager,
-                mCarContext.getString(R.string.route_preview_template_demo_title),
-                new RoutePreviewDemoScreen(mCarContext)
+                mCarContext.getString(R.string.junction_image_demo_title),
+                new JunctionImageDemoScreen(mCarContext)
+        ));
+
+        screenList.add(createRow(
+                screenManager,
+                mCarContext.getString(R.string.navigating_demo_title),
+                new NavigatingDemoScreen(mCarContext)
         ));
 
         screenList.add(createRow(
@@ -88,33 +87,6 @@
                 mCarContext.getString(R.string.nav_map_template_demo_title),
                 new NavigationMapOnlyScreen(mCarContext)
         ));
-
-        screenList.add(createRow(
-                screenManager,
-                mCarContext.getString(R.string.place_list_template_demo_title),
-                new PlaceListTemplateBrowseDemoScreen(mCarContext)
-        ));
-
-        screenList.add(createRow(
-                screenManager,
-                mCarContext.getString(R.string.map_template_list_demo_title),
-                new MapTemplateWithListDemoScreen(mCarContext)
-        ));
-
-        screenList.add(createRow(
-                screenManager,
-                mCarContext.getString(R.string.map_template_pane_demo_title),
-                new MapTemplateWithPaneDemoScreen(mCarContext)
-        ));
-
-        if (mCarContext.getCarAppApiLevel() >= CarAppApiLevels.LEVEL_6) {
-            screenList.add(
-                    createRow(
-                            screenManager,
-                            mCarContext.getString(R.string.map_template_toggle_demo_title),
-                            new MapTemplateWithToggleDemoScreen(mCarContext)));
-        }
-
         return screenList;
     }
 
@@ -124,13 +96,6 @@
         return mCarContext.getString(R.string.nav_demos_title);
     }
 
-    private CarIcon buildCarIcon(int imageId) {
-        return new CarIcon.Builder(
-                IconCompat.createWithResource(
-                        mCarContext,
-                        imageId))
-                .build();
-    }
 
     private Row createRow(ScreenManager screenManager, String title, Screen screen) {
         return new Row.Builder()
@@ -138,13 +103,4 @@
                 .setOnClickListener(() -> screenManager.push(screen))
                 .build();
     }
-
-    private Row createRow(ScreenManager screenManager, CarIcon image, String title, Screen screen) {
-        return new Row.Builder()
-                .setImage(image)
-                .setTitle(title)
-                .setOnClickListener(() -> screenManager.push(screen))
-                .setBrowsable(true)
-                .build();
-    }
 }
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/MapWithContentDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/MapWithContentDemoScreen.java
new file mode 100644
index 0000000..49d9658
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/MapWithContentDemoScreen.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.car.app.sample.showcase.common.screens.mapdemos;
+
+import static androidx.car.app.model.Action.BACK;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.Screen;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.ListTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.screens.mapdemos.mapwithcontent.MapTemplateWithListDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.mapdemos.mapwithcontent.MapTemplateWithPaneDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.mapdemos.mapwithcontent.MapTemplateWithToggleDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.mapdemos.mapwithcontent.MapWithGridTemplateDemoScreen;
+import androidx.car.app.sample.showcase.common.screens.mapdemos.mapwithcontent.MapWithMessageTemplateDemoScreen;
+import androidx.car.app.versioning.CarAppApiLevels;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** A screen demonstrating different template layouts. */
+public final class MapWithContentDemoScreen extends Screen {
+    public MapWithContentDemoScreen(@NonNull CarContext carContext) {
+        super(carContext);
+    }
+
+    @NonNull
+    @Override
+    public Template onGetTemplate() {
+        List<Row> screenList = new ArrayList<>();
+        if (getCarContext().getCarAppApiLevel() >= CarAppApiLevels.LEVEL_7) {
+            screenList.add(buildRowForTemplate(new MapWithMessageTemplateDemoScreen(
+                    getCarContext()),
+                    R.string.map_with_message_demo_title));
+            screenList.add(buildRowForTemplate(new MapWithGridTemplateDemoScreen(getCarContext()),
+                    R.string.map_with_grid_demo_title));
+        }
+
+        screenList.add(buildRowForTemplate(new MapTemplateWithListDemoScreen(getCarContext()),
+                R.string.map_template_list_demo_title));
+        screenList.add(buildRowForTemplate(new MapTemplateWithPaneDemoScreen(getCarContext()),
+                R.string.map_template_pane_demo_title));
+
+        if (getCarContext().getCarAppApiLevel() >= CarAppApiLevels.LEVEL_6) {
+            screenList.add(buildRowForTemplate(new MapTemplateWithToggleDemoScreen(getCarContext()),
+                    R.string.map_template_toggle_demo_title));
+        }
+
+        ItemList.Builder listBuilder = new ItemList.Builder();
+
+        for (int i = 0; i < screenList.size(); i++) {
+            listBuilder.addItem(screenList.get(i));
+        }
+
+        return new ListTemplate.Builder()
+                .setSingleList(listBuilder.build())
+                .setTitle(getCarContext().getString(R.string.map_demos_title))
+                .setHeaderAction(BACK)
+                .build();
+    }
+
+    private Row buildRowForTemplate(Screen screen, int title) {
+        return new Row.Builder()
+                .setTitle(getCarContext().getString(title))
+                .setOnClickListener(() -> getScreenManager().push(screen))
+                .build();
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/PlaceListNavigationTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/PlaceListNavigationTemplateDemoScreen.java
similarity index 96%
rename from car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/PlaceListNavigationTemplateDemoScreen.java
rename to car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/PlaceListNavigationTemplateDemoScreen.java
index 479d959..b69fcec 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/PlaceListNavigationTemplateDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/PlaceListNavigationTemplateDemoScreen.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.car.app.sample.showcase.common.screens.navigationdemos;
+package androidx.car.app.sample.showcase.common.screens.mapdemos;
 
 import static androidx.car.app.CarToast.LENGTH_SHORT;
 
@@ -34,7 +34,7 @@
 import androidx.car.app.navigation.model.PlaceListNavigationTemplate;
 import androidx.car.app.sample.showcase.common.R;
 import androidx.car.app.sample.showcase.common.common.SamplePlaces;
-import androidx.car.app.sample.showcase.common.screens.navigationdemos.navigationtemplates.RoutingDemoModels;
+import androidx.car.app.sample.showcase.common.screens.navigationdemos.RoutingDemoModels;
 import androidx.core.graphics.drawable.IconCompat;
 
 /** Creates a screen using the {@link PlaceListNavigationTemplate} */
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/PlaceListTemplateBrowseDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/PlaceListTemplateBrowseDemoScreen.java
similarity index 97%
rename from car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/PlaceListTemplateBrowseDemoScreen.java
rename to car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/PlaceListTemplateBrowseDemoScreen.java
index 3026fb0..7001be4 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/PlaceListTemplateBrowseDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/PlaceListTemplateBrowseDemoScreen.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.car.app.sample.showcase.common.screens.navigationdemos;
+package androidx.car.app.sample.showcase.common.screens.mapdemos;
 
 import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
 import static android.Manifest.permission.ACCESS_FINE_LOCATION;
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/PlaceListTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/PlaceListTemplateDemoScreen.java
similarity index 92%
rename from car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/PlaceListTemplateDemoScreen.java
rename to car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/PlaceListTemplateDemoScreen.java
index c65d6d3..b7d3b8f 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/PlaceListTemplateDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/PlaceListTemplateDemoScreen.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.car.app.sample.showcase.common.screens.navigationdemos;
+package androidx.car.app.sample.showcase.common.screens.mapdemos;
 
 import androidx.annotation.NonNull;
 import androidx.car.app.CarContext;
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/RoutePreviewDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/RoutePreviewDemoScreen.java
similarity index 97%
rename from car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/RoutePreviewDemoScreen.java
rename to car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/RoutePreviewDemoScreen.java
index ac3e620..55e317b 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/RoutePreviewDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/RoutePreviewDemoScreen.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.car.app.sample.showcase.common.screens.navigationdemos;
+package androidx.car.app.sample.showcase.common.screens.mapdemos;
 
 import static androidx.car.app.CarToast.LENGTH_LONG;
 import static androidx.car.app.CarToast.LENGTH_SHORT;
@@ -36,7 +36,7 @@
 import androidx.car.app.model.Template;
 import androidx.car.app.navigation.model.RoutePreviewNavigationTemplate;
 import androidx.car.app.sample.showcase.common.R;
-import androidx.car.app.sample.showcase.common.screens.navigationdemos.navigationtemplates.RoutingDemoModels;
+import androidx.car.app.sample.showcase.common.screens.navigationdemos.RoutingDemoModels;
 import androidx.core.graphics.drawable.IconCompat;
 
 import java.util.concurrent.TimeUnit;
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/MapTemplateWithListDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/mapwithcontent/MapTemplateWithListDemoScreen.java
similarity index 97%
rename from car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/MapTemplateWithListDemoScreen.java
rename to car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/mapwithcontent/MapTemplateWithListDemoScreen.java
index 83a4c93..d8ea173 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/MapTemplateWithListDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/mapwithcontent/MapTemplateWithListDemoScreen.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.car.app.sample.showcase.common.screens.navigationdemos;
+package androidx.car.app.sample.showcase.common.screens.mapdemos.mapwithcontent;
 
 import static androidx.car.app.CarToast.LENGTH_LONG;
 import static androidx.car.app.CarToast.LENGTH_SHORT;
@@ -37,7 +37,7 @@
 import androidx.car.app.navigation.model.MapController;
 import androidx.car.app.navigation.model.MapTemplate;
 import androidx.car.app.sample.showcase.common.R;
-import androidx.car.app.sample.showcase.common.screens.navigationdemos.navigationtemplates.RoutingDemoModels;
+import androidx.car.app.sample.showcase.common.screens.navigationdemos.RoutingDemoModels;
 import androidx.car.app.versioning.CarAppApiLevels;
 import androidx.core.graphics.drawable.IconCompat;
 
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/MapTemplateWithPaneDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/mapwithcontent/MapTemplateWithPaneDemoScreen.java
similarity index 98%
rename from car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/MapTemplateWithPaneDemoScreen.java
rename to car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/mapwithcontent/MapTemplateWithPaneDemoScreen.java
index da1987c..d28f05b 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/MapTemplateWithPaneDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/mapwithcontent/MapTemplateWithPaneDemoScreen.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.car.app.sample.showcase.common.screens.navigationdemos;
+package androidx.car.app.sample.showcase.common.screens.mapdemos.mapwithcontent;
 
 import static androidx.car.app.CarToast.LENGTH_LONG;
 import static androidx.car.app.CarToast.LENGTH_SHORT;
@@ -40,7 +40,7 @@
 import androidx.car.app.navigation.model.MapController;
 import androidx.car.app.navigation.model.MapTemplate;
 import androidx.car.app.sample.showcase.common.R;
-import androidx.car.app.sample.showcase.common.screens.navigationdemos.navigationtemplates.RoutingDemoModels;
+import androidx.car.app.sample.showcase.common.screens.navigationdemos.RoutingDemoModels;
 import androidx.car.app.versioning.CarAppApiLevels;
 import androidx.core.graphics.drawable.IconCompat;
 
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/MapTemplateWithToggleDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/mapwithcontent/MapTemplateWithToggleDemoScreen.java
similarity index 96%
rename from car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/MapTemplateWithToggleDemoScreen.java
rename to car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/mapwithcontent/MapTemplateWithToggleDemoScreen.java
index b05c551..6ed0d07 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/MapTemplateWithToggleDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/mapwithcontent/MapTemplateWithToggleDemoScreen.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.car.app.sample.showcase.common.screens.navigationdemos;
+package androidx.car.app.sample.showcase.common.screens.mapdemos.mapwithcontent;
 
 import androidx.annotation.NonNull;
 import androidx.car.app.CarContext;
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/mapwithcontent/MapWithGridTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/mapwithcontent/MapWithGridTemplateDemoScreen.java
new file mode 100644
index 0000000..0186731
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/mapwithcontent/MapWithGridTemplateDemoScreen.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.car.app.sample.showcase.common.screens.mapdemos.mapwithcontent;
+
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.Screen;
+import androidx.car.app.annotations.ExperimentalCarApi;
+import androidx.car.app.annotations.RequiresCarApi;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.ActionStrip;
+import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.GridItem;
+import androidx.car.app.model.GridTemplate;
+import androidx.car.app.model.ItemList;
+import androidx.car.app.model.Template;
+import androidx.car.app.navigation.model.MapWithContentTemplate;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.core.graphics.drawable.IconCompat;
+
+/** Simple demo of how to present a map template with a list. */
+public class MapWithGridTemplateDemoScreen extends Screen {
+
+    public MapWithGridTemplateDemoScreen(@NonNull CarContext carContext) {
+        super(carContext);
+    }
+
+    @ExperimentalCarApi
+    @RequiresCarApi(7)
+    @NonNull
+    @Override
+    public Template onGetTemplate() {
+        ItemList.Builder gridItemListBuilder = new ItemList.Builder();
+        for (int i = 0; i <= 7; i++) {
+            gridItemListBuilder.addItem(createGridItem());
+        }
+
+
+        GridTemplate gridTemplate = new GridTemplate.Builder()
+                .setSingleList(gridItemListBuilder.build())
+                .setHeaderAction(Action.BACK)
+                .setTitle("Report?")
+                .build();
+
+
+        ActionStrip actionStrip = new ActionStrip.Builder()
+                .addAction(
+                        new Action.Builder()
+                                .setOnClickListener(
+                                        () -> CarToast.makeText(
+                                                        getCarContext(),
+                                                        getCarContext().getString(
+                                                                R.string.bug_reported_toast_msg),
+                                                        CarToast.LENGTH_SHORT)
+                                                .show())
+                                .setIcon(
+                                        new CarIcon.Builder(
+                                                IconCompat.createWithResource(
+                                                        getCarContext(),
+                                                        R.drawable.ic_bug_report_24px))
+                                                .build())
+                                .setFlags(Action.FLAG_IS_PERSISTENT)
+                                .build())
+                .build();
+
+        MapWithContentTemplate.Builder builder = new MapWithContentTemplate.Builder()
+                .setContentTemplate(gridTemplate)
+                .setActionStrip(actionStrip);
+
+        return builder.build();
+    }
+
+    private GridItem createGridItem() {
+        return new GridItem.Builder()
+                .setImage(new CarIcon.Builder(IconCompat.createWithResource(getCarContext(),
+                        R.drawable.ic_fastfood_white_48dp)).build())
+                .setTitle("Primary")
+                .setText("Secondary")
+                .setOnClickListener(() -> CarToast.makeText(
+                                getCarContext(),
+                                "Clicked!",
+                                CarToast.LENGTH_SHORT)
+                        .show())
+                .build();
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/mapwithcontent/MapWithMessageTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/mapwithcontent/MapWithMessageTemplateDemoScreen.java
new file mode 100644
index 0000000..d71cb7a
--- /dev/null
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/mapdemos/mapwithcontent/MapWithMessageTemplateDemoScreen.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.app.sample.showcase.common.screens.mapdemos.mapwithcontent;
+
+import android.content.res.TypedArray;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.Screen;
+import androidx.car.app.annotations.ExperimentalCarApi;
+import androidx.car.app.annotations.RequiresCarApi;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.ActionStrip;
+import androidx.car.app.model.CarColor;
+import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.MessageTemplate;
+import androidx.car.app.model.Template;
+import androidx.car.app.navigation.model.MapController;
+import androidx.car.app.navigation.model.MapWithContentTemplate;
+import androidx.car.app.sample.showcase.common.R;
+import androidx.car.app.sample.showcase.common.screens.navigationdemos.RoutingDemoModels;
+import androidx.core.graphics.drawable.IconCompat;
+
+/** Simple demo of how to present a map template with a list. */
+public class MapWithMessageTemplateDemoScreen extends Screen {
+    TypedArray mTypedArray =
+            getCarContext().obtainStyledAttributes(R.style.CarAppTheme, R.styleable.ShowcaseTheme);
+    CarColor mIconTintColor =
+            CarColor.createCustom(
+                    mTypedArray.getColor(R.styleable.ShowcaseTheme_markerIconTintColor, -1),
+                    mTypedArray.getColor(R.styleable.ShowcaseTheme_markerIconTintColorDark, -1));
+    public MapWithMessageTemplateDemoScreen(@NonNull CarContext carContext) {
+        super(carContext);
+    }
+
+    @ExperimentalCarApi
+    @RequiresCarApi(7)
+    @NonNull
+    @Override
+    public Template onGetTemplate() {
+
+        MessageTemplate messageTemplate = new MessageTemplate.Builder("Continue to Google "
+                + "Kirkland Urban WA 98101?")
+                .setHeaderAction(Action.BACK)
+                .setTitle("Drive to Google Kirkland")
+                .setIcon(new CarIcon.Builder(
+                        IconCompat.createWithResource(
+                                getCarContext(),
+                                R.drawable.ic_commute_24px))
+                        .setTint(mIconTintColor)
+                        .build())
+                .addAction(new Action.Builder()
+                        .setOnClickListener(() -> {
+                            CarToast.makeText(
+                                    getCarContext(),
+                                    "Let's start navigation",
+                                    CarToast.LENGTH_SHORT
+                            ).show();
+                        })
+                        .setTitle("Navigate").build())
+                .addAction(
+                        new Action.Builder()
+                                .setOnClickListener(() -> {
+                                    CarToast.makeText(
+                                            getCarContext(),
+                                            "Quitting navigation",
+                                            CarToast.LENGTH_SHORT
+                                    ).show();
+                                })
+                                .setTitle("Cancel").build())
+
+                .build();
+
+
+        MapController mapController = new MapController.Builder()
+                .setMapActionStrip(RoutingDemoModels.getMapActionStrip(getCarContext()))
+                .build();
+        MapWithContentTemplate.Builder builder = new MapWithContentTemplate.Builder()
+                .setContentTemplate(messageTemplate)
+                .setMapController(mapController)
+                .setActionStrip(
+                        new ActionStrip.Builder()
+                                .addAction(
+                                        new Action.Builder()
+                                                .setTitle(getCarContext().getString(
+                                                        R.string.search_action_title))
+                                                .setOnClickListener(() -> {
+                                                })
+                                                .build())
+                                .build());
+
+        return builder.build();
+    }
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/navigationtemplates/ArrivedDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/ArrivedDemoScreen.java
similarity index 96%
rename from car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/navigationtemplates/ArrivedDemoScreen.java
rename to car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/ArrivedDemoScreen.java
index 55dd41a..6a5a0bd 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/navigationtemplates/ArrivedDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/ArrivedDemoScreen.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.car.app.sample.showcase.common.screens.navigationdemos.navigationtemplates;
+package androidx.car.app.sample.showcase.common.screens.navigationdemos;
 
 import androidx.annotation.NonNull;
 import androidx.car.app.CarContext;
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/navigationtemplates/JunctionImageDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/JunctionImageDemoScreen.java
similarity index 96%
rename from car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/navigationtemplates/JunctionImageDemoScreen.java
rename to car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/JunctionImageDemoScreen.java
index 4d0a7c7..e665a98 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/navigationtemplates/JunctionImageDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/JunctionImageDemoScreen.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.car.app.sample.showcase.common.screens.navigationdemos.navigationtemplates;
+package androidx.car.app.sample.showcase.common.screens.navigationdemos;
 
 import androidx.annotation.NonNull;
 import androidx.car.app.CarContext;
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/navigationtemplates/LoadingDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/LoadingDemoScreen.java
similarity index 94%
rename from car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/navigationtemplates/LoadingDemoScreen.java
rename to car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/LoadingDemoScreen.java
index 9cfdf58..85618ef 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/navigationtemplates/LoadingDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/LoadingDemoScreen.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.car.app.sample.showcase.common.screens.navigationdemos.navigationtemplates;
+package androidx.car.app.sample.showcase.common.screens.navigationdemos;
 
 import androidx.annotation.NonNull;
 import androidx.car.app.CarContext;
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/navigationtemplates/NavigatingDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/NavigatingDemoScreen.java
similarity index 96%
rename from car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/navigationtemplates/NavigatingDemoScreen.java
rename to car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/NavigatingDemoScreen.java
index eeb08d1..677b4f7 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/navigationtemplates/NavigatingDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/NavigatingDemoScreen.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.car.app.sample.showcase.common.screens.navigationdemos.navigationtemplates;
+package androidx.car.app.sample.showcase.common.screens.navigationdemos;
 
 import androidx.annotation.NonNull;
 import androidx.car.app.CarContext;
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/NavigationTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/NavigationTemplateDemoScreen.java
deleted file mode 100644
index 26846cb..0000000
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/NavigationTemplateDemoScreen.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 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.car.app.sample.showcase.common.screens.navigationdemos;
-
-import androidx.annotation.NonNull;
-import androidx.car.app.CarContext;
-import androidx.car.app.Screen;
-import androidx.car.app.model.Action;
-import androidx.car.app.model.ItemList;
-import androidx.car.app.model.ListTemplate;
-import androidx.car.app.model.Row;
-import androidx.car.app.model.Template;
-import androidx.car.app.sample.showcase.common.R;
-import androidx.car.app.sample.showcase.common.screens.navigationdemos.navigationtemplates.ArrivedDemoScreen;
-import androidx.car.app.sample.showcase.common.screens.navigationdemos.navigationtemplates.JunctionImageDemoScreen;
-import androidx.car.app.sample.showcase.common.screens.navigationdemos.navigationtemplates.LoadingDemoScreen;
-import androidx.car.app.sample.showcase.common.screens.navigationdemos.navigationtemplates.NavigatingDemoScreen;
-
-/** A screen showing a demos for the navigation template in different states. */
-public final class NavigationTemplateDemoScreen extends Screen {
-    public NavigationTemplateDemoScreen(@NonNull CarContext carContext) {
-        super(carContext);
-    }
-
-    @NonNull
-    @Override
-    public Template onGetTemplate() {
-        ItemList.Builder listBuilder = new ItemList.Builder();
-
-        listBuilder.addItem(
-                new Row.Builder()
-                        .setTitle(getCarContext().getString(R.string.loading_demo_title))
-                        .setOnClickListener(
-                                () ->
-                                        getScreenManager()
-                                                .push(new LoadingDemoScreen(getCarContext())))
-                        .build());
-
-        listBuilder.addItem(
-                new Row.Builder()
-                        .setTitle(getCarContext().getString(R.string.navigating_demo_title))
-                        .setOnClickListener(
-                                () ->
-                                        getScreenManager()
-                                                .push(new NavigatingDemoScreen(getCarContext())))
-                        .build());
-
-        listBuilder.addItem(
-                new Row.Builder()
-                        .setTitle(getCarContext().getString(R.string.arrived_demo_title))
-                        .setOnClickListener(
-                                () ->
-                                        getScreenManager()
-                                                .push(new ArrivedDemoScreen(getCarContext())))
-                        .build());
-
-        listBuilder.addItem(
-                new Row.Builder()
-                        .setTitle(getCarContext().getString(R.string.junction_image_demo_title))
-                        .setOnClickListener(
-                                () ->
-                                        getScreenManager()
-                                                .push(new JunctionImageDemoScreen(getCarContext())))
-                        .build());
-
-        return new ListTemplate.Builder()
-                .setSingleList(listBuilder.build())
-                .setTitle(getCarContext().getString(R.string.nav_template_demos_title))
-                .setHeaderAction(Action.BACK)
-                .build();
-    }
-}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/navigationtemplates/RoutingDemoModels.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/RoutingDemoModels.java
similarity index 99%
rename from car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/navigationtemplates/RoutingDemoModels.java
rename to car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/RoutingDemoModels.java
index bdc3771..4b3f0bf 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/navigationtemplates/RoutingDemoModels.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/RoutingDemoModels.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.car.app.sample.showcase.common.screens.navigationdemos.navigationtemplates;
+package androidx.car.app.sample.showcase.common.screens.navigationdemos;
 
 import static androidx.car.app.model.Action.FLAG_DEFAULT;
 import static androidx.car.app.model.Action.FLAG_PRIMARY;
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-pt-rBR/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-pt-rBR/strings.xml
index 146b7c7..7119538 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-pt-rBR/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-pt-rBR/strings.xml
@@ -239,7 +239,7 @@
     <string name="additional_text" msgid="8410289578276941586">"Consulte nossos Termos de Serviço"</string>
     <string name="google_sign_in" msgid="6556259799319701727">"Login do Google"</string>
     <string name="use_pin" msgid="7850893299484337431">"Usar PIN"</string>
-    <string name="qr_code" msgid="5487041647280777397">"Código QR"</string>
+    <string name="qr_code" msgid="5487041647280777397">"QR code"</string>
     <string name="sign_in_template_not_supported_text" msgid="7184733753948837646">"Seu host não oferece suporte ao modelo de login"</string>
     <string name="sign_in_template_not_supported_title" msgid="4892883228898541764">"Host incompatível"</string>
     <string name="email_hint" msgid="7205549445477319606">"E-mail"</string>
@@ -251,7 +251,7 @@
     <string name="password_hint" msgid="2869107073860012864">"senha"</string>
     <string name="password_sign_in_instruction_prefix" msgid="9105788349198243508">"Nome de usuário"</string>
     <string name="pin_sign_in_instruction" msgid="2288691296234360441">"Digite este PIN no smartphone"</string>
-    <string name="qr_code_sign_in_title" msgid="8137070561006464518">"Ler o Código QR para fazer login"</string>
+    <string name="qr_code_sign_in_title" msgid="8137070561006464518">"Ler o QR code para fazer login"</string>
     <string name="sign_in_with_google_title" msgid="8043752000786977249">"Fazer login com o Google"</string>
     <string name="provider_sign_in_instruction" msgid="7586815688292506743">"Use este botão para concluir o Login do Google"</string>
     <string name="sign_in_complete_text" msgid="8423984266325680606">"Você fez login."</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-pt/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-pt/strings.xml
index 146b7c7..7119538 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-pt/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-pt/strings.xml
@@ -239,7 +239,7 @@
     <string name="additional_text" msgid="8410289578276941586">"Consulte nossos Termos de Serviço"</string>
     <string name="google_sign_in" msgid="6556259799319701727">"Login do Google"</string>
     <string name="use_pin" msgid="7850893299484337431">"Usar PIN"</string>
-    <string name="qr_code" msgid="5487041647280777397">"Código QR"</string>
+    <string name="qr_code" msgid="5487041647280777397">"QR code"</string>
     <string name="sign_in_template_not_supported_text" msgid="7184733753948837646">"Seu host não oferece suporte ao modelo de login"</string>
     <string name="sign_in_template_not_supported_title" msgid="4892883228898541764">"Host incompatível"</string>
     <string name="email_hint" msgid="7205549445477319606">"E-mail"</string>
@@ -251,7 +251,7 @@
     <string name="password_hint" msgid="2869107073860012864">"senha"</string>
     <string name="password_sign_in_instruction_prefix" msgid="9105788349198243508">"Nome de usuário"</string>
     <string name="pin_sign_in_instruction" msgid="2288691296234360441">"Digite este PIN no smartphone"</string>
-    <string name="qr_code_sign_in_title" msgid="8137070561006464518">"Ler o Código QR para fazer login"</string>
+    <string name="qr_code_sign_in_title" msgid="8137070561006464518">"Ler o QR code para fazer login"</string>
     <string name="sign_in_with_google_title" msgid="8043752000786977249">"Fazer login com o Google"</string>
     <string name="provider_sign_in_instruction" msgid="7586815688292506743">"Use este botão para concluir o Login do Google"</string>
     <string name="sign_in_complete_text" msgid="8423984266325680606">"Você fez login."</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values/strings.xml
index f0623a2..948d8fab6 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values/strings.xml
@@ -492,4 +492,8 @@
     <string name="route_options_demo_title">Route options</string>
   <string name="avoid_highways_row_title">Avoid highways</string>
   <string name="avoid_ferries_row_title">Avoid ferry</string>
+  <string name="map_demos_title">Map Demos</string>
+  <string name="map_with_content_demo_title">Map With Content Demos</string>
+  <string name="map_with_message_demo_title">Map With Message Template Demo</string>
+  <string name="map_with_grid_demo_title">Map With Grid Template Demo</string>
 </resources>
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/StrongSkippingModeTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/StrongSkippingModeTransformTests.kt
index 716343f..d66d65b5 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/StrongSkippingModeTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/StrongSkippingModeTransformTests.kt
@@ -16,18 +16,41 @@
 
 package androidx.compose.compiler.plugins.kotlin
 
+import androidx.compose.compiler.plugins.kotlin.facade.SourceFile
+import java.io.File
+import org.intellij.lang.annotations.Language
 import org.jetbrains.kotlin.config.CompilerConfiguration
+import org.jetbrains.kotlin.ir.util.DumpIrTreeOptions
+import org.jetbrains.kotlin.ir.util.DumpIrTreeVisitor
+import org.junit.Assert.assertEquals
 import org.junit.Test
+import org.junit.runners.Parameterized
 
-class StrongSkippingModeTransformTests(useFir: Boolean) :
-    FunctionBodySkippingTransformTestsBase(useFir) {
+class StrongSkippingModeTransformTests(
+    useFir: Boolean,
+    private val intrinsicRememberEnabled: Boolean
+) : AbstractIrTransformTest(useFir) {
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "useFir = {0}, intrinsicRemember = {1}")
+        fun data() = arrayOf<Any>(
+            arrayOf(false, false),
+            arrayOf(false, true),
+            arrayOf(true, false),
+            arrayOf(true, true)
+        )
+    }
 
     override fun CompilerConfiguration.updateConfiguration() {
         put(ComposeConfiguration.STRONG_SKIPPING_ENABLED_KEY, true)
+        put(
+            ComposeConfiguration.INTRINSIC_REMEMBER_OPTIMIZATION_ENABLED_KEY,
+            intrinsicRememberEnabled
+        )
     }
 
     @Test
-    fun testSingleStableParam(): Unit = comparisonPropagation(
+    fun testSingleStableParam(): Unit = verifyMemoization(
         """
             class Foo(val value: Int = 0)
             @Composable fun A(x: Foo) {}
@@ -41,7 +64,7 @@
     )
 
     @Test
-    fun testSingleUnstableParam(): Unit = comparisonPropagation(
+    fun testSingleUnstableParam(): Unit = verifyMemoization(
         """
             @Composable fun A(x: Foo) {}
             class Foo(var value: Int = 0)
@@ -55,7 +78,7 @@
     )
 
     @Test
-    fun testSingleNullableUnstableParam(): Unit = comparisonPropagation(
+    fun testSingleNullableUnstableParam(): Unit = verifyMemoization(
         """
             @Composable fun A(x: Foo?) {}
             class Foo(var value: Int = 0)
@@ -69,7 +92,7 @@
     )
 
     @Test
-    fun testSingleOptionalUnstableParam(): Unit = comparisonPropagation(
+    fun testSingleOptionalUnstableParam(): Unit = verifyMemoization(
         """
             @Composable fun A(x: Foo?) {}
             class Foo(var value: Int = 0)
@@ -83,7 +106,7 @@
     )
 
     @Test
-    fun testRuntimeStableParam(): Unit = comparisonPropagation(
+    fun testRuntimeStableParam(): Unit = verifyMemoization(
         """
             @Composable fun A(x: Int) {}
         """,
@@ -98,7 +121,7 @@
     )
 
     @Test
-    fun testStableUnstableParams(): Unit = comparisonPropagation(
+    fun testStableUnstableParams(): Unit = verifyMemoization(
         """
             @Composable fun A(x: Int = 0, y: Int = 0): Int { return 10 }
             class Foo(var value: Int = 0)
@@ -120,25 +143,25 @@
     )
 
     @Test
-    fun testStaticDefaultParam() = comparisonPropagation(
+    fun testStaticDefaultParam() = verifyMemoization(
         """
             @Composable
             fun A(i: Int, list: List<Int>? = null, set: Set<Int> = emptySet()) {}
-        """.trimIndent(),
+        """,
         """
             @Composable
             fun Test(i: Int) {
                 A(i)
             }
-        """.trimIndent()
+        """
     )
 
     @Test
-    fun testMemoizingUnstableCapturesInLambda() = comparisonPropagation(
+    fun testMemoizingUnstableCapturesInLambda() = verifyMemoization(
         """
             @Composable fun A(x: Int = 0, y: Int = 0): Int { return 10 }
             class Foo(var value: Int = 0)
-        """.trimIndent(),
+        """,
         """
             @Composable
             fun Test() {
@@ -149,12 +172,12 @@
     )
 
     @Test
-    fun testDontMemoizeLambda() = comparisonPropagation(
+    fun testDontMemoizeLambda() = verifyMemoization(
         """
             @Composable fun A(x: Int = 0, y: Int = 0): Int { return 10 }
             class Foo(var value: Int = 0)
             fun Lam(x: ()->Unit) { x() }
-        """.trimIndent(),
+        """,
         """
             import androidx.compose.runtime.DontMemoize
 
@@ -168,12 +191,12 @@
     )
 
     @Test
-    fun testMemoizingUnstableFunctionParameterInLambda() = comparisonPropagation(
+    fun testMemoizingUnstableFunctionParameterInLambda() = verifyMemoization(
         """
             @Composable fun A(x: Int = 0, y: Int = 0): Int { return 10 }
             class Foo(var value: Int = 0)
             class Bar(val value: Int = 0)
-        """.trimIndent(),
+        """,
         """
             @Composable
             fun Test(foo: Foo, bar: Bar) {
@@ -186,12 +209,12 @@
     )
 
     @Test
-    fun testMemoizingComposableLambda() = comparisonPropagation(
+    fun testMemoizingComposableLambda() = verifyMemoization(
         """
             @Composable fun A(x: Int = 0, y: Int = 0): Int { return 10 }
             class Foo(var value: Int = 0)
             class Bar(val value: Int = 0)
-        """.trimIndent(),
+        """,
         """
             @Composable
             fun Test(foo: Foo, bar: Bar) {
@@ -204,12 +227,12 @@
     )
 
     @Test
-    fun testMemoizingStableAndUnstableCapturesInLambda() = comparisonPropagation(
+    fun testMemoizingStableAndUnstableCapturesInLambda() = verifyMemoization(
         """
             @Composable fun A(x: Int = 0, y: Int = 0): Int { return 10 }
             class Foo(var value: Int = 0)
             class Bar(val value: Int = 0)
-        """.trimIndent(),
+        """,
         """
             @Composable
             fun Test() {
@@ -224,13 +247,13 @@
     )
 
     @Test
-    fun testFunctionInterfaceMemorized() = comparisonPropagation(
+    fun testFunctionInterfaceMemorized() = verifyMemoization(
         """
             fun interface TestFunInterface {
                 fun compute(value: Int)
             }
             fun use(@Suppress("UNUSED_PARAMETER") v: Int) {}
-        """.trimIndent(),
+        """,
         """
             @Composable fun TestMemoizedFun(compute: TestFunInterface) {}
             @Composable fun Test() {
@@ -244,11 +267,11 @@
                     use(capture)
                 }
             }
-        """.trimIndent()
+        """
     )
 
     @Test
-    fun testVarArgs() = comparisonPropagation(
+    fun testVarArgs() = verifyMemoization(
         "",
         """
             @Composable fun Varargs(vararg ints: Int) {
@@ -256,11 +279,11 @@
             @Composable fun Test() {
                 Varargs(1, 2, 3)
             }
-        """.trimIndent()
+        """
     )
 
     @Test
-    fun testRuntimeStableVarArgs() = comparisonPropagation(
+    fun testRuntimeStableVarArgs() = verifyMemoization(
         """
             @Composable fun A(x: Int) {}
         """,
@@ -275,7 +298,7 @@
     )
 
     @Test
-    fun testUnstableReceiverFunctionReferenceMemoized() = comparisonPropagation(
+    fun testUnstableReceiverFunctionReferenceMemoized() = verifyMemoization(
         """
             class Unstable(var qux: Int = 0) { fun method(arg1: Int) {} }
             val unstable = Unstable()
@@ -289,7 +312,7 @@
     )
 
     @Test
-    fun testUnstableExtensionReceiverFunctionReferenceMemoized() = comparisonPropagation(
+    fun testUnstableExtensionReceiverFunctionReferenceMemoized() = verifyMemoization(
         """
             class Unstable(var foo: Int = 0)
             fun Unstable.method(arg1: Int) {}
@@ -302,4 +325,55 @@
             }
         """
     )
+
+    private fun verifyMemoization(
+        @Language("kotlin")
+        unchecked: String,
+        @Language("kotlin")
+        checked: String,
+        dumpTree: Boolean = false
+    ) {
+        val source = """
+            import androidx.compose.runtime.Composable
+            import androidx.compose.runtime.NonRestartableComposable
+            import androidx.compose.runtime.ReadOnlyComposable
+
+            $checked
+        """
+
+        val extra = """
+             import androidx.compose.runtime.Composable
+
+            $unchecked
+            fun used(x: Any?) {}
+        """
+
+        // verify that generated keys are path independent
+        val module1 = dumpIrWithPath(source, extra, "/home/folder1")
+        val module2 = dumpIrWithPath(source, extra, "/home/folder2")
+
+        assertEquals(module1, module2)
+
+        verifyGoldenComposeIrTransform(
+            source,
+            extra,
+            dumpTree = dumpTree
+        )
+    }
+
+    private fun dumpIrWithPath(
+        source: String,
+        extra: String,
+        path: String,
+    ): String {
+        val sourceFile1 = SourceFile("Test.kt", source, path = path)
+        val extraFile1 = SourceFile("Extra.kt", extra, path = path)
+        return compileToIr(listOf(sourceFile1, extraFile1)).files.joinToString("\n") {
+            buildString {
+                val fileShortName = it.fileEntry.name.takeLastWhile { it != File.separatorChar }
+                appendLine("IrFile: $fileShortName")
+                it.acceptChildren(DumpIrTreeVisitor(this, DumpIrTreeOptions()), "")
+            }
+        }
+    }
 }
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/facade/KotlinCompilerFacade.kt b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/facade/KotlinCompilerFacade.kt
index 1a5e4b8..8498808 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/facade/KotlinCompilerFacade.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/jvmTest/kotlin/androidx/compose/compiler/plugins/kotlin/facade/KotlinCompilerFacade.kt
@@ -48,7 +48,8 @@
 class SourceFile(
     val name: String,
     val source: String,
-    private val ignoreParseErrors: Boolean = false
+    private val ignoreParseErrors: Boolean = false,
+    val path: String = ""
 ) {
     fun toKtFile(project: Project): KtFile {
         val shortName = name.substring(name.lastIndexOf('/') + 1).let {
@@ -60,7 +61,7 @@
             KotlinLanguage.INSTANCE,
             StringUtilRt.convertLineSeparators(source)
         ) {
-            override fun getPath(): String = "/$name"
+            override fun getPath(): String = "${this@SourceFile.path}/$name"
         }
 
         virtualFile.charset = StandardCharsets.UTF_8
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testDontMemoizeLambda\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testDontMemoizeLambda\133useFir = false, intrinsicRemember = false\135.txt"
similarity index 100%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testDontMemoizeLambda\133useFir = false\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testDontMemoizeLambda\133useFir = false, intrinsicRemember = false\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testDontMemoizeLambda\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testDontMemoizeLambda\133useFir = false, intrinsicRemember = true\135.txt"
similarity index 100%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testDontMemoizeLambda\133useFir = true\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testDontMemoizeLambda\133useFir = false, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testDontMemoizeLambda\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testDontMemoizeLambda\133useFir = true, intrinsicRemember = false\135.txt"
similarity index 100%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testDontMemoizeLambda\133useFir = false\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testDontMemoizeLambda\133useFir = true, intrinsicRemember = false\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testDontMemoizeLambda\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testDontMemoizeLambda\133useFir = true, intrinsicRemember = true\135.txt"
similarity index 100%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testDontMemoizeLambda\133useFir = true\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testDontMemoizeLambda\133useFir = true, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testDontMemoizeLambdasInMarkedFunction\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testDontMemoizeLambdasInMarkedFunction\133useFir = false\135.txt"
deleted file mode 100644
index 01d55915..0000000
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testDontMemoizeLambdasInMarkedFunction\133useFir = false\135.txt"
+++ /dev/null
@@ -1,45 +0,0 @@
-//
-// Source
-// ------------------------------------------
-
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.NonRestartableComposable
-import androidx.compose.runtime.ReadOnlyComposable
-
-
-import androidx.compose.runtime.DontMemoize
-
-@Composable
-@DontMemoize
-fun Test() {
-    val foo = Foo(0)
-    val lambda = { foo }
-}
-
-//
-// Transformed IR
-// ------------------------------------------
-
-@Composable
-@DontMemoize
-fun Test(%composer: Composer?, %changed: Int) {
-  %composer = %composer.startRestartGroup(<>)
-  sourceInformation(%composer, "C(Test)")
-  if (%changed != 0 || !%composer.skipping) {
-    if (isTraceInProgress()) {
-      traceEventStart(<>, %changed, -1, <>)
-    }
-    val foo = Foo(0)
-    val lambda = {
-      foo
-    }
-    if (isTraceInProgress()) {
-      traceEventEnd()
-    }
-  } else {
-    %composer.skipToGroupEnd()
-  }
-  %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
-    Test(%composer, updateChangedFlags(%changed or 0b0001))
-  }
-}
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testDontMemoizeLambdasInMarkedFunction\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testDontMemoizeLambdasInMarkedFunction\133useFir = true\135.txt"
deleted file mode 100644
index 01d55915..0000000
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testDontMemoizeLambdasInMarkedFunction\133useFir = true\135.txt"
+++ /dev/null
@@ -1,45 +0,0 @@
-//
-// Source
-// ------------------------------------------
-
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.NonRestartableComposable
-import androidx.compose.runtime.ReadOnlyComposable
-
-
-import androidx.compose.runtime.DontMemoize
-
-@Composable
-@DontMemoize
-fun Test() {
-    val foo = Foo(0)
-    val lambda = { foo }
-}
-
-//
-// Transformed IR
-// ------------------------------------------
-
-@Composable
-@DontMemoize
-fun Test(%composer: Composer?, %changed: Int) {
-  %composer = %composer.startRestartGroup(<>)
-  sourceInformation(%composer, "C(Test)")
-  if (%changed != 0 || !%composer.skipping) {
-    if (isTraceInProgress()) {
-      traceEventStart(<>, %changed, -1, <>)
-    }
-    val foo = Foo(0)
-    val lambda = {
-      foo
-    }
-    if (isTraceInProgress()) {
-      traceEventEnd()
-    }
-  } else {
-    %composer.skipToGroupEnd()
-  }
-  %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
-    Test(%composer, updateChangedFlags(%changed or 0b0001))
-  }
-}
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = false, intrinsicRemember = false\135.txt"
similarity index 86%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = false\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = false, intrinsicRemember = false\135.txt"
index ce69dc9..45de354 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = false, intrinsicRemember = false\135.txt"
@@ -3,10 +3,11 @@
 // ------------------------------------------
 
 import androidx.compose.runtime.Composable
-            import androidx.compose.runtime.NonRestartableComposable
-            import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.NonRestartableComposable
+import androidx.compose.runtime.ReadOnlyComposable
 
-            @Composable fun TestMemoizedFun(compute: TestFunInterface) {}
+
+@Composable fun TestMemoizedFun(compute: TestFunInterface) {}
 @Composable fun Test() {
     val capture = 0
     TestMemoizedFun {
@@ -53,13 +54,13 @@
     }, %composer, 0b0110)
     TestMemoizedFun(<block>{
       %composer.startReplaceableGroup(<>)
-      val tmp0_group = %composer.cache(false) {
+      val tmpCache = %composer.cache(%composer.changed(capture)) {
         TestFunInterface { it: Int ->
           use(capture)
         }
       }
       %composer.endReplaceableGroup()
-      tmp0_group
+      tmpCache
     }, %composer, 0)
     if (isTraceInProgress()) {
       traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = false, intrinsicRemember = true\135.txt"
similarity index 89%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = false\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = false, intrinsicRemember = true\135.txt"
index ce69dc9..be475f6 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = false, intrinsicRemember = true\135.txt"
@@ -3,10 +3,11 @@
 // ------------------------------------------
 
 import androidx.compose.runtime.Composable
-            import androidx.compose.runtime.NonRestartableComposable
-            import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.NonRestartableComposable
+import androidx.compose.runtime.ReadOnlyComposable
 
-            @Composable fun TestMemoizedFun(compute: TestFunInterface) {}
+
+@Composable fun TestMemoizedFun(compute: TestFunInterface) {}
 @Composable fun Test() {
     val capture = 0
     TestMemoizedFun {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = true, intrinsicRemember = false\135.txt"
similarity index 86%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = false\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = true, intrinsicRemember = false\135.txt"
index ce69dc9..45de354 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = true, intrinsicRemember = false\135.txt"
@@ -3,10 +3,11 @@
 // ------------------------------------------
 
 import androidx.compose.runtime.Composable
-            import androidx.compose.runtime.NonRestartableComposable
-            import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.NonRestartableComposable
+import androidx.compose.runtime.ReadOnlyComposable
 
-            @Composable fun TestMemoizedFun(compute: TestFunInterface) {}
+
+@Composable fun TestMemoizedFun(compute: TestFunInterface) {}
 @Composable fun Test() {
     val capture = 0
     TestMemoizedFun {
@@ -53,13 +54,13 @@
     }, %composer, 0b0110)
     TestMemoizedFun(<block>{
       %composer.startReplaceableGroup(<>)
-      val tmp0_group = %composer.cache(false) {
+      val tmpCache = %composer.cache(%composer.changed(capture)) {
         TestFunInterface { it: Int ->
           use(capture)
         }
       }
       %composer.endReplaceableGroup()
-      tmp0_group
+      tmpCache
     }, %composer, 0)
     if (isTraceInProgress()) {
       traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = true, intrinsicRemember = true\135.txt"
similarity index 89%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = false\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = true, intrinsicRemember = true\135.txt"
index ce69dc9..be475f6 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = true, intrinsicRemember = true\135.txt"
@@ -3,10 +3,11 @@
 // ------------------------------------------
 
 import androidx.compose.runtime.Composable
-            import androidx.compose.runtime.NonRestartableComposable
-            import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.NonRestartableComposable
+import androidx.compose.runtime.ReadOnlyComposable
 
-            @Composable fun TestMemoizedFun(compute: TestFunInterface) {}
+
+@Composable fun TestMemoizedFun(compute: TestFunInterface) {}
 @Composable fun Test() {
     val capture = 0
     TestMemoizedFun {
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = true\135.txt"
deleted file mode 100644
index ce69dc9..0000000
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testFunctionInterfaceMemorized\133useFir = true\135.txt"
+++ /dev/null
@@ -1,73 +0,0 @@
-//
-// Source
-// ------------------------------------------
-
-import androidx.compose.runtime.Composable
-            import androidx.compose.runtime.NonRestartableComposable
-            import androidx.compose.runtime.ReadOnlyComposable
-
-            @Composable fun TestMemoizedFun(compute: TestFunInterface) {}
-@Composable fun Test() {
-    val capture = 0
-    TestMemoizedFun {
-        // no captures
-        use(it)
-    }
-    TestMemoizedFun {
-        // stable captures
-        use(capture)
-    }
-}
-
-//
-// Transformed IR
-// ------------------------------------------
-
-@Composable
-fun TestMemoizedFun(compute: TestFunInterface, %composer: Composer?, %changed: Int) {
-  %composer = %composer.startRestartGroup(<>)
-  if (%changed and 0b0001 != 0 || !%composer.skipping) {
-    if (isTraceInProgress()) {
-      traceEventStart(<>, %changed, -1, <>)
-    }
-    if (isTraceInProgress()) {
-      traceEventEnd()
-    }
-  } else {
-    %composer.skipToGroupEnd()
-  }
-  %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
-    TestMemoizedFun(compute, %composer, updateChangedFlags(%changed or 0b0001))
-  }
-}
-@Composable
-fun Test(%composer: Composer?, %changed: Int) {
-  %composer = %composer.startRestartGroup(<>)
-  if (%changed != 0 || !%composer.skipping) {
-    if (isTraceInProgress()) {
-      traceEventStart(<>, %changed, -1, <>)
-    }
-    val capture = 0
-    TestMemoizedFun(TestFunInterface { it: Int ->
-      use(it)
-    }, %composer, 0b0110)
-    TestMemoizedFun(<block>{
-      %composer.startReplaceableGroup(<>)
-      val tmp0_group = %composer.cache(false) {
-        TestFunInterface { it: Int ->
-          use(capture)
-        }
-      }
-      %composer.endReplaceableGroup()
-      tmp0_group
-    }, %composer, 0)
-    if (isTraceInProgress()) {
-      traceEventEnd()
-    }
-  } else {
-    %composer.skipToGroupEnd()
-  }
-  %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
-    Test(%composer, updateChangedFlags(%changed or 0b0001))
-  }
-}
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingComposableLambda\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingComposableLambda\133useFir = false, intrinsicRemember = false\135.txt"
similarity index 100%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingComposableLambda\133useFir = false\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingComposableLambda\133useFir = false, intrinsicRemember = false\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingComposableLambda\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingComposableLambda\133useFir = false, intrinsicRemember = true\135.txt"
similarity index 100%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingComposableLambda\133useFir = true\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingComposableLambda\133useFir = false, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingComposableLambda\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingComposableLambda\133useFir = true, intrinsicRemember = false\135.txt"
similarity index 100%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingComposableLambda\133useFir = false\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingComposableLambda\133useFir = true, intrinsicRemember = false\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingComposableLambda\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingComposableLambda\133useFir = true, intrinsicRemember = true\135.txt"
similarity index 100%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingComposableLambda\133useFir = true\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingComposableLambda\133useFir = true, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingStableAndUnstableCapturesInLambda\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingStableAndUnstableCapturesInLambda\133useFir = false, intrinsicRemember = false\135.txt"
similarity index 90%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingStableAndUnstableCapturesInLambda\133useFir = false\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingStableAndUnstableCapturesInLambda\133useFir = false, intrinsicRemember = false\135.txt"
index 34d44a4..994ca87 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingStableAndUnstableCapturesInLambda\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingStableAndUnstableCapturesInLambda\133useFir = false, intrinsicRemember = false\135.txt"
@@ -32,14 +32,14 @@
     val bar = Bar(1)
     val lambda = <block>{
       %composer.startReplaceableGroup(<>)
-      val tmp0_group = %composer.cache(%composer.changedInstance(foo) or %composer.changed(bar)) {
+      val tmpCache = %composer.cache(%composer.changedInstance(foo) or %composer.changed(bar)) {
         {
           foo
           bar
         }
       }
       %composer.endReplaceableGroup()
-      tmp0_group
+      tmpCache
     }
     if (isTraceInProgress()) {
       traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingStableAndUnstableCapturesInLambda\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingStableAndUnstableCapturesInLambda\133useFir = false, intrinsicRemember = true\135.txt"
similarity index 100%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingStableAndUnstableCapturesInLambda\133useFir = true\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingStableAndUnstableCapturesInLambda\133useFir = false, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingStableAndUnstableCapturesInLambda\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingStableAndUnstableCapturesInLambda\133useFir = true, intrinsicRemember = false\135.txt"
similarity index 90%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingStableAndUnstableCapturesInLambda\133useFir = false\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingStableAndUnstableCapturesInLambda\133useFir = true, intrinsicRemember = false\135.txt"
index 34d44a4..994ca87 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingStableAndUnstableCapturesInLambda\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingStableAndUnstableCapturesInLambda\133useFir = true, intrinsicRemember = false\135.txt"
@@ -32,14 +32,14 @@
     val bar = Bar(1)
     val lambda = <block>{
       %composer.startReplaceableGroup(<>)
-      val tmp0_group = %composer.cache(%composer.changedInstance(foo) or %composer.changed(bar)) {
+      val tmpCache = %composer.cache(%composer.changedInstance(foo) or %composer.changed(bar)) {
         {
           foo
           bar
         }
       }
       %composer.endReplaceableGroup()
-      tmp0_group
+      tmpCache
     }
     if (isTraceInProgress()) {
       traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingStableAndUnstableCapturesInLambda\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingStableAndUnstableCapturesInLambda\133useFir = true, intrinsicRemember = true\135.txt"
similarity index 100%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingStableAndUnstableCapturesInLambda\133useFir = true\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingStableAndUnstableCapturesInLambda\133useFir = true, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableCapturesInLambda\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableCapturesInLambda\133useFir = false, intrinsicRemember = false\135.txt"
similarity index 91%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableCapturesInLambda\133useFir = false\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableCapturesInLambda\133useFir = false, intrinsicRemember = false\135.txt"
index 1684495..a399ea5 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableCapturesInLambda\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableCapturesInLambda\133useFir = false, intrinsicRemember = false\135.txt"
@@ -27,13 +27,13 @@
     val foo = Foo(0)
     val lambda = <block>{
       %composer.startReplaceableGroup(<>)
-      val tmp0_group = %composer.cache(%composer.changedInstance(foo)) {
+      val tmpCache = %composer.cache(%composer.changedInstance(foo)) {
         {
           foo
         }
       }
       %composer.endReplaceableGroup()
-      tmp0_group
+      tmpCache
     }
     if (isTraceInProgress()) {
       traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableCapturesInLambda\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableCapturesInLambda\133useFir = false, intrinsicRemember = true\135.txt"
similarity index 100%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableCapturesInLambda\133useFir = true\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableCapturesInLambda\133useFir = false, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableCapturesInLambda\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableCapturesInLambda\133useFir = true, intrinsicRemember = false\135.txt"
similarity index 91%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableCapturesInLambda\133useFir = false\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableCapturesInLambda\133useFir = true, intrinsicRemember = false\135.txt"
index 1684495..a399ea5 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableCapturesInLambda\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableCapturesInLambda\133useFir = true, intrinsicRemember = false\135.txt"
@@ -27,13 +27,13 @@
     val foo = Foo(0)
     val lambda = <block>{
       %composer.startReplaceableGroup(<>)
-      val tmp0_group = %composer.cache(%composer.changedInstance(foo)) {
+      val tmpCache = %composer.cache(%composer.changedInstance(foo)) {
         {
           foo
         }
       }
       %composer.endReplaceableGroup()
-      tmp0_group
+      tmpCache
     }
     if (isTraceInProgress()) {
       traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableCapturesInLambda\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableCapturesInLambda\133useFir = true, intrinsicRemember = true\135.txt"
similarity index 100%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableCapturesInLambda\133useFir = true\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableCapturesInLambda\133useFir = true, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableFunctionParameterInLambda\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableFunctionParameterInLambda\133useFir = false, intrinsicRemember = false\135.txt"
similarity index 90%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableFunctionParameterInLambda\133useFir = false\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableFunctionParameterInLambda\133useFir = false, intrinsicRemember = false\135.txt"
index ad46339..ddced50 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableFunctionParameterInLambda\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableFunctionParameterInLambda\133useFir = false, intrinsicRemember = false\135.txt"
@@ -35,14 +35,14 @@
     }
     val lambda = <block>{
       %composer.startReplaceableGroup(<>)
-      val tmp0_group = %composer.cache(%composer.changedInstance(foo) or %dirty and 0b01110000 == 0b00100000) {
+      val tmpCache = %composer.cache(%composer.changedInstance(foo) or %composer.changed(bar)) {
         {
           foo
           bar
         }
       }
       %composer.endReplaceableGroup()
-      tmp0_group
+      tmpCache
     }
     if (isTraceInProgress()) {
       traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableFunctionParameterInLambda\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableFunctionParameterInLambda\133useFir = false, intrinsicRemember = true\135.txt"
similarity index 100%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableFunctionParameterInLambda\133useFir = true\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableFunctionParameterInLambda\133useFir = false, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableFunctionParameterInLambda\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableFunctionParameterInLambda\133useFir = true, intrinsicRemember = false\135.txt"
similarity index 90%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableFunctionParameterInLambda\133useFir = false\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableFunctionParameterInLambda\133useFir = true, intrinsicRemember = false\135.txt"
index ad46339..ddced50 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableFunctionParameterInLambda\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableFunctionParameterInLambda\133useFir = true, intrinsicRemember = false\135.txt"
@@ -35,14 +35,14 @@
     }
     val lambda = <block>{
       %composer.startReplaceableGroup(<>)
-      val tmp0_group = %composer.cache(%composer.changedInstance(foo) or %dirty and 0b01110000 == 0b00100000) {
+      val tmpCache = %composer.cache(%composer.changedInstance(foo) or %composer.changed(bar)) {
         {
           foo
           bar
         }
       }
       %composer.endReplaceableGroup()
-      tmp0_group
+      tmpCache
     }
     if (isTraceInProgress()) {
       traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableFunctionParameterInLambda\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableFunctionParameterInLambda\133useFir = true, intrinsicRemember = true\135.txt"
similarity index 100%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableFunctionParameterInLambda\133useFir = true\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testMemoizingUnstableFunctionParameterInLambda\133useFir = true, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableParam\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableParam\133useFir = false, intrinsicRemember = false\135.txt"
similarity index 100%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableParam\133useFir = false\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableParam\133useFir = false, intrinsicRemember = false\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableParam\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableParam\133useFir = false, intrinsicRemember = true\135.txt"
similarity index 100%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableParam\133useFir = true\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableParam\133useFir = false, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableParam\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableParam\133useFir = true, intrinsicRemember = false\135.txt"
similarity index 100%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableParam\133useFir = false\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableParam\133useFir = true, intrinsicRemember = false\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableParam\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableParam\133useFir = true, intrinsicRemember = true\135.txt"
similarity index 100%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableParam\133useFir = true\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableParam\133useFir = true, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableVarArgs\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableVarArgs\133useFir = false, intrinsicRemember = false\135.txt"
similarity index 100%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableVarArgs\133useFir = false\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableVarArgs\133useFir = false, intrinsicRemember = false\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableVarArgs\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableVarArgs\133useFir = false, intrinsicRemember = true\135.txt"
similarity index 100%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableVarArgs\133useFir = true\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableVarArgs\133useFir = false, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableVarArgs\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableVarArgs\133useFir = true, intrinsicRemember = false\135.txt"
similarity index 100%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableVarArgs\133useFir = false\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableVarArgs\133useFir = true, intrinsicRemember = false\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableVarArgs\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableVarArgs\133useFir = true, intrinsicRemember = true\135.txt"
similarity index 100%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableVarArgs\133useFir = true\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testRuntimeStableVarArgs\133useFir = true, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleNullableUnstableParam\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleNullableUnstableParam\133useFir = false, intrinsicRemember = false\135.txt"
similarity index 100%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleNullableUnstableParam\133useFir = false\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleNullableUnstableParam\133useFir = false, intrinsicRemember = false\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleNullableUnstableParam\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleNullableUnstableParam\133useFir = false, intrinsicRemember = true\135.txt"
similarity index 100%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleNullableUnstableParam\133useFir = true\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleNullableUnstableParam\133useFir = false, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleNullableUnstableParam\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleNullableUnstableParam\133useFir = true, intrinsicRemember = false\135.txt"
similarity index 100%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleNullableUnstableParam\133useFir = false\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleNullableUnstableParam\133useFir = true, intrinsicRemember = false\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleNullableUnstableParam\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleNullableUnstableParam\133useFir = true, intrinsicRemember = true\135.txt"
similarity index 100%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleNullableUnstableParam\133useFir = true\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleNullableUnstableParam\133useFir = true, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleOptionalUnstableParam\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleOptionalUnstableParam\133useFir = false, intrinsicRemember = false\135.txt"
similarity index 100%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleOptionalUnstableParam\133useFir = false\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleOptionalUnstableParam\133useFir = false, intrinsicRemember = false\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleOptionalUnstableParam\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleOptionalUnstableParam\133useFir = false, intrinsicRemember = true\135.txt"
similarity index 100%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleOptionalUnstableParam\133useFir = true\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleOptionalUnstableParam\133useFir = false, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleOptionalUnstableParam\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleOptionalUnstableParam\133useFir = true, intrinsicRemember = false\135.txt"
similarity index 100%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleOptionalUnstableParam\133useFir = false\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleOptionalUnstableParam\133useFir = true, intrinsicRemember = false\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleOptionalUnstableParam\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleOptionalUnstableParam\133useFir = true, intrinsicRemember = true\135.txt"
similarity index 100%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleOptionalUnstableParam\133useFir = true\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleOptionalUnstableParam\133useFir = true, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleStableParam\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleStableParam\133useFir = false, intrinsicRemember = false\135.txt"
similarity index 100%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleStableParam\133useFir = false\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleStableParam\133useFir = false, intrinsicRemember = false\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleStableParam\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleStableParam\133useFir = false, intrinsicRemember = true\135.txt"
similarity index 100%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleStableParam\133useFir = true\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleStableParam\133useFir = false, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleStableParam\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleStableParam\133useFir = true, intrinsicRemember = false\135.txt"
similarity index 100%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleStableParam\133useFir = false\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleStableParam\133useFir = true, intrinsicRemember = false\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleStableParam\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleStableParam\133useFir = true, intrinsicRemember = true\135.txt"
similarity index 100%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleStableParam\133useFir = true\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleStableParam\133useFir = true, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleUnstableParam\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleUnstableParam\133useFir = false, intrinsicRemember = false\135.txt"
similarity index 100%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleUnstableParam\133useFir = false\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleUnstableParam\133useFir = false, intrinsicRemember = false\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleUnstableParam\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleUnstableParam\133useFir = false, intrinsicRemember = true\135.txt"
similarity index 100%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleUnstableParam\133useFir = true\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleUnstableParam\133useFir = false, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleUnstableParam\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleUnstableParam\133useFir = true, intrinsicRemember = false\135.txt"
similarity index 100%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleUnstableParam\133useFir = false\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleUnstableParam\133useFir = true, intrinsicRemember = false\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleUnstableParam\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleUnstableParam\133useFir = true, intrinsicRemember = true\135.txt"
similarity index 100%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleUnstableParam\133useFir = true\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testSingleUnstableParam\133useFir = true, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStableUnstableParams\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStableUnstableParams\133useFir = false, intrinsicRemember = false\135.txt"
similarity index 100%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStableUnstableParams\133useFir = false\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStableUnstableParams\133useFir = false, intrinsicRemember = false\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStableUnstableParams\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStableUnstableParams\133useFir = false, intrinsicRemember = true\135.txt"
similarity index 100%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStableUnstableParams\133useFir = true\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStableUnstableParams\133useFir = false, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStableUnstableParams\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStableUnstableParams\133useFir = true, intrinsicRemember = false\135.txt"
similarity index 100%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStableUnstableParams\133useFir = false\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStableUnstableParams\133useFir = true, intrinsicRemember = false\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStableUnstableParams\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStableUnstableParams\133useFir = true, intrinsicRemember = true\135.txt"
similarity index 100%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStableUnstableParams\133useFir = true\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStableUnstableParams\133useFir = true, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = false, intrinsicRemember = false\135.txt"
similarity index 84%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = false\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = false, intrinsicRemember = false\135.txt"
index bf4c3ce..6a0231d 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = false, intrinsicRemember = false\135.txt"
@@ -3,10 +3,11 @@
 // ------------------------------------------
 
 import androidx.compose.runtime.Composable
-            import androidx.compose.runtime.NonRestartableComposable
-            import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.NonRestartableComposable
+import androidx.compose.runtime.ReadOnlyComposable
 
-            @Composable
+
+@Composable
 fun Test(i: Int) {
     A(i)
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = false, intrinsicRemember = true\135.txt"
similarity index 84%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = false\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = false, intrinsicRemember = true\135.txt"
index bf4c3ce..6a0231d 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = false, intrinsicRemember = true\135.txt"
@@ -3,10 +3,11 @@
 // ------------------------------------------
 
 import androidx.compose.runtime.Composable
-            import androidx.compose.runtime.NonRestartableComposable
-            import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.NonRestartableComposable
+import androidx.compose.runtime.ReadOnlyComposable
 
-            @Composable
+
+@Composable
 fun Test(i: Int) {
     A(i)
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = true, intrinsicRemember = false\135.txt"
similarity index 84%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = false\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = true, intrinsicRemember = false\135.txt"
index bf4c3ce..6a0231d 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = true, intrinsicRemember = false\135.txt"
@@ -3,10 +3,11 @@
 // ------------------------------------------
 
 import androidx.compose.runtime.Composable
-            import androidx.compose.runtime.NonRestartableComposable
-            import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.NonRestartableComposable
+import androidx.compose.runtime.ReadOnlyComposable
 
-            @Composable
+
+@Composable
 fun Test(i: Int) {
     A(i)
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = true, intrinsicRemember = true\135.txt"
similarity index 84%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = false\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = true, intrinsicRemember = true\135.txt"
index bf4c3ce..6a0231d 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = true, intrinsicRemember = true\135.txt"
@@ -3,10 +3,11 @@
 // ------------------------------------------
 
 import androidx.compose.runtime.Composable
-            import androidx.compose.runtime.NonRestartableComposable
-            import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.NonRestartableComposable
+import androidx.compose.runtime.ReadOnlyComposable
 
-            @Composable
+
+@Composable
 fun Test(i: Int) {
     A(i)
 }
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = true\135.txt"
deleted file mode 100644
index bf4c3ce..0000000
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testStaticDefaultParam\133useFir = true\135.txt"
+++ /dev/null
@@ -1,39 +0,0 @@
-//
-// Source
-// ------------------------------------------
-
-import androidx.compose.runtime.Composable
-            import androidx.compose.runtime.NonRestartableComposable
-            import androidx.compose.runtime.ReadOnlyComposable
-
-            @Composable
-fun Test(i: Int) {
-    A(i)
-}
-
-//
-// Transformed IR
-// ------------------------------------------
-
-@Composable
-fun Test(i: Int, %composer: Composer?, %changed: Int) {
-  %composer = %composer.startRestartGroup(<>)
-  val %dirty = %changed
-  if (%changed and 0b0110 == 0) {
-    %dirty = %dirty or if (%composer.changed(i)) 0b0100 else 0b0010
-  }
-  if (%dirty and 0b0011 != 0b0010 || !%composer.skipping) {
-    if (isTraceInProgress()) {
-      traceEventStart(<>, %dirty, -1, <>)
-    }
-    A(i, null, null, %composer, 0b1110 and %dirty, 0b0110)
-    if (isTraceInProgress()) {
-      traceEventEnd()
-    }
-  } else {
-    %composer.skipToGroupEnd()
-  }
-  %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
-    Test(i, %composer, updateChangedFlags(%changed or 0b0001))
-  }
-}
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = false, intrinsicRemember = false\135.txt"
similarity index 91%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = false\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = false, intrinsicRemember = false\135.txt"
index 32f8d7f..046e728 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = false, intrinsicRemember = false\135.txt"
@@ -26,11 +26,11 @@
     val x = <block>{
       val tmp0 = unstable
       %composer.startReplaceableGroup(<>)
-      val tmp0_group = %composer.cache(%composer.changedInstance(tmp0)) {
+      val tmpCache = %composer.cache(%composer.changedInstance(tmp0)) {
         tmp0::method
       }
       %composer.endReplaceableGroup()
-      tmp0_group
+      tmpCache
     }
     if (isTraceInProgress()) {
       traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = false, intrinsicRemember = true\135.txt"
similarity index 100%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = true\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = false, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = true, intrinsicRemember = false\135.txt"
similarity index 91%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = false\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = true, intrinsicRemember = false\135.txt"
index 32f8d7f..046e728 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = true, intrinsicRemember = false\135.txt"
@@ -26,11 +26,11 @@
     val x = <block>{
       val tmp0 = unstable
       %composer.startReplaceableGroup(<>)
-      val tmp0_group = %composer.cache(%composer.changedInstance(tmp0)) {
+      val tmpCache = %composer.cache(%composer.changedInstance(tmp0)) {
         tmp0::method
       }
       %composer.endReplaceableGroup()
-      tmp0_group
+      tmpCache
     }
     if (isTraceInProgress()) {
       traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = true, intrinsicRemember = true\135.txt"
similarity index 100%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = true\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = true, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableReceiverFunctionReferenceMemoized\133useFir = false, intrinsicRemember = false\135.txt"
similarity index 91%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = false\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableReceiverFunctionReferenceMemoized\133useFir = false, intrinsicRemember = false\135.txt"
index 32f8d7f..046e728 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableReceiverFunctionReferenceMemoized\133useFir = false, intrinsicRemember = false\135.txt"
@@ -26,11 +26,11 @@
     val x = <block>{
       val tmp0 = unstable
       %composer.startReplaceableGroup(<>)
-      val tmp0_group = %composer.cache(%composer.changedInstance(tmp0)) {
+      val tmpCache = %composer.cache(%composer.changedInstance(tmp0)) {
         tmp0::method
       }
       %composer.endReplaceableGroup()
-      tmp0_group
+      tmpCache
     }
     if (isTraceInProgress()) {
       traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableReceiverFunctionReferenceMemoized\133useFir = false, intrinsicRemember = true\135.txt"
similarity index 100%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = true\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableReceiverFunctionReferenceMemoized\133useFir = false, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableReceiverFunctionReferenceMemoized\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableReceiverFunctionReferenceMemoized\133useFir = false\135.txt"
deleted file mode 100644
index 32f8d7f..0000000
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableReceiverFunctionReferenceMemoized\133useFir = false\135.txt"
+++ /dev/null
@@ -1,44 +0,0 @@
-//
-// Source
-// ------------------------------------------
-
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.NonRestartableComposable
-import androidx.compose.runtime.ReadOnlyComposable
-
-
-@Composable
-fun Something() {
-    val x = unstable::method
-}
-
-//
-// Transformed IR
-// ------------------------------------------
-
-@Composable
-fun Something(%composer: Composer?, %changed: Int) {
-  %composer = %composer.startRestartGroup(<>)
-  if (%changed != 0 || !%composer.skipping) {
-    if (isTraceInProgress()) {
-      traceEventStart(<>, %changed, -1, <>)
-    }
-    val x = <block>{
-      val tmp0 = unstable
-      %composer.startReplaceableGroup(<>)
-      val tmp0_group = %composer.cache(%composer.changedInstance(tmp0)) {
-        tmp0::method
-      }
-      %composer.endReplaceableGroup()
-      tmp0_group
-    }
-    if (isTraceInProgress()) {
-      traceEventEnd()
-    }
-  } else {
-    %composer.skipToGroupEnd()
-  }
-  %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
-    Something(%composer, updateChangedFlags(%changed or 0b0001))
-  }
-}
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableReceiverFunctionReferenceMemoized\133useFir = true, intrinsicRemember = false\135.txt"
similarity index 91%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = false\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableReceiverFunctionReferenceMemoized\133useFir = true, intrinsicRemember = false\135.txt"
index 32f8d7f..046e728 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableReceiverFunctionReferenceMemoized\133useFir = true, intrinsicRemember = false\135.txt"
@@ -26,11 +26,11 @@
     val x = <block>{
       val tmp0 = unstable
       %composer.startReplaceableGroup(<>)
-      val tmp0_group = %composer.cache(%composer.changedInstance(tmp0)) {
+      val tmpCache = %composer.cache(%composer.changedInstance(tmp0)) {
         tmp0::method
       }
       %composer.endReplaceableGroup()
-      tmp0_group
+      tmpCache
     }
     if (isTraceInProgress()) {
       traceEventEnd()
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableReceiverFunctionReferenceMemoized\133useFir = true, intrinsicRemember = true\135.txt"
similarity index 100%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableExtensionReceiverFunctionReferenceMemoized\133useFir = true\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableReceiverFunctionReferenceMemoized\133useFir = true, intrinsicRemember = true\135.txt"
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableReceiverFunctionReferenceMemoized\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableReceiverFunctionReferenceMemoized\133useFir = true\135.txt"
deleted file mode 100644
index 32f8d7f..0000000
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testUnstableReceiverFunctionReferenceMemoized\133useFir = true\135.txt"
+++ /dev/null
@@ -1,44 +0,0 @@
-//
-// Source
-// ------------------------------------------
-
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.NonRestartableComposable
-import androidx.compose.runtime.ReadOnlyComposable
-
-
-@Composable
-fun Something() {
-    val x = unstable::method
-}
-
-//
-// Transformed IR
-// ------------------------------------------
-
-@Composable
-fun Something(%composer: Composer?, %changed: Int) {
-  %composer = %composer.startRestartGroup(<>)
-  if (%changed != 0 || !%composer.skipping) {
-    if (isTraceInProgress()) {
-      traceEventStart(<>, %changed, -1, <>)
-    }
-    val x = <block>{
-      val tmp0 = unstable
-      %composer.startReplaceableGroup(<>)
-      val tmp0_group = %composer.cache(%composer.changedInstance(tmp0)) {
-        tmp0::method
-      }
-      %composer.endReplaceableGroup()
-      tmp0_group
-    }
-    if (isTraceInProgress()) {
-      traceEventEnd()
-    }
-  } else {
-    %composer.skipToGroupEnd()
-  }
-  %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
-    Something(%composer, updateChangedFlags(%changed or 0b0001))
-  }
-}
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = false, intrinsicRemember = false\135.txt"
similarity index 89%
rename from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = false\135.txt"
rename to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = false, intrinsicRemember = false\135.txt"
index d398e4a..4fbeb8d 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = false, intrinsicRemember = false\135.txt"
@@ -3,10 +3,11 @@
 // ------------------------------------------
 
 import androidx.compose.runtime.Composable
-            import androidx.compose.runtime.NonRestartableComposable
-            import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.NonRestartableComposable
+import androidx.compose.runtime.ReadOnlyComposable
 
-            @Composable fun Varargs(vararg ints: Int) {
+
+@Composable fun Varargs(vararg ints: Int) {
 }
 @Composable fun Test() {
     Varargs(1, 2, 3)
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = false, intrinsicRemember = true\135.txt"
similarity index 89%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = false\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = false, intrinsicRemember = true\135.txt"
index d398e4a..4fbeb8d 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = false, intrinsicRemember = true\135.txt"
@@ -3,10 +3,11 @@
 // ------------------------------------------
 
 import androidx.compose.runtime.Composable
-            import androidx.compose.runtime.NonRestartableComposable
-            import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.NonRestartableComposable
+import androidx.compose.runtime.ReadOnlyComposable
 
-            @Composable fun Varargs(vararg ints: Int) {
+
+@Composable fun Varargs(vararg ints: Int) {
 }
 @Composable fun Test() {
     Varargs(1, 2, 3)
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = true, intrinsicRemember = false\135.txt"
similarity index 89%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = false\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = true, intrinsicRemember = false\135.txt"
index d398e4a..4fbeb8d 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = true, intrinsicRemember = false\135.txt"
@@ -3,10 +3,11 @@
 // ------------------------------------------
 
 import androidx.compose.runtime.Composable
-            import androidx.compose.runtime.NonRestartableComposable
-            import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.NonRestartableComposable
+import androidx.compose.runtime.ReadOnlyComposable
 
-            @Composable fun Varargs(vararg ints: Int) {
+
+@Composable fun Varargs(vararg ints: Int) {
 }
 @Composable fun Test() {
     Varargs(1, 2, 3)
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = false\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = true, intrinsicRemember = true\135.txt"
similarity index 89%
copy from "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = false\135.txt"
copy to "compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = true, intrinsicRemember = true\135.txt"
index d398e4a..4fbeb8d 100644
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = false\135.txt"
+++ "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = true, intrinsicRemember = true\135.txt"
@@ -3,10 +3,11 @@
 // ------------------------------------------
 
 import androidx.compose.runtime.Composable
-            import androidx.compose.runtime.NonRestartableComposable
-            import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.NonRestartableComposable
+import androidx.compose.runtime.ReadOnlyComposable
 
-            @Composable fun Varargs(vararg ints: Int) {
+
+@Composable fun Varargs(vararg ints: Int) {
 }
 @Composable fun Test() {
     Varargs(1, 2, 3)
diff --git "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = true\135.txt" "b/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = true\135.txt"
deleted file mode 100644
index d398e4a..0000000
--- "a/compose/compiler/compiler-hosted/integration-tests/src/test/resources/golden/androidx.compose.compiler.plugins.kotlin.StrongSkippingModeTransformTests/testVarArgs\133useFir = true\135.txt"
+++ /dev/null
@@ -1,64 +0,0 @@
-//
-// Source
-// ------------------------------------------
-
-import androidx.compose.runtime.Composable
-            import androidx.compose.runtime.NonRestartableComposable
-            import androidx.compose.runtime.ReadOnlyComposable
-
-            @Composable fun Varargs(vararg ints: Int) {
-}
-@Composable fun Test() {
-    Varargs(1, 2, 3)
-}
-
-//
-// Transformed IR
-// ------------------------------------------
-
-@Composable
-fun Varargs(ints: IntArray, %composer: Composer?, %changed: Int) {
-  %composer = %composer.startRestartGroup(<>)
-  val %dirty = %changed
-  %composer.startMovableGroup(<>, ints.size)
-  val <iterator> = ints.iterator()
-  while (<iterator>.hasNext()) {
-    val value = <iterator>.next()
-    %dirty = %dirty or if (%composer.changed(value)) 0b0100 else 0
-  }
-  %composer.endMovableGroup()
-  if (%dirty and 0b1110 == 0) {
-    %dirty = %dirty or 0b0010
-  }
-  if (%dirty and 0b0001 != 0 || !%composer.skipping) {
-    if (isTraceInProgress()) {
-      traceEventStart(<>, %dirty, -1, <>)
-    }
-    if (isTraceInProgress()) {
-      traceEventEnd()
-    }
-  } else {
-    %composer.skipToGroupEnd()
-  }
-  %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
-    Varargs(*ints, %composer, updateChangedFlags(%changed or 0b0001))
-  }
-}
-@Composable
-fun Test(%composer: Composer?, %changed: Int) {
-  %composer = %composer.startRestartGroup(<>)
-  if (%changed != 0 || !%composer.skipping) {
-    if (isTraceInProgress()) {
-      traceEventStart(<>, %changed, -1, <>)
-    }
-    Varargs(1, 2, 3, %composer, 0)
-    if (isTraceInProgress()) {
-      traceEventEnd()
-    }
-  } else {
-    %composer.skipToGroupEnd()
-  }
-  %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
-    Test(%composer, updateChangedFlags(%changed or 0b0001))
-  }
-}
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
index 90acac0..99cacc5 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/lower/ComposerLambdaMemoization.kt
@@ -91,6 +91,7 @@
 import org.jetbrains.kotlin.ir.util.hasAnnotation
 import org.jetbrains.kotlin.ir.util.isFunctionOrKFunction
 import org.jetbrains.kotlin.ir.util.isLocal
+import org.jetbrains.kotlin.ir.util.kotlinFqName
 import org.jetbrains.kotlin.ir.util.patchDeclarationParents
 import org.jetbrains.kotlin.ir.util.primaryConstructor
 import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
@@ -991,12 +992,8 @@
             calculation
         )
 
-        val fileName = currentFile
-            ?.fileEntry
-            ?.name
-            ?.let { PackagePartClassUtils.getFilePartShortName(it) }
-            ?: ""
-        val key = fileName.hashCode() + expression.startOffset
+        val fqName = currentFunctionContext?.declaration?.kotlinFqName?.asString()
+        val key = fqName.hashCode() + expression.startOffset
         // Wrap the cached expression in a replaceable group
         val cacheTmpVar = irTemporary(cache, "tmpCache")
         return cacheTmpVar.wrap(
diff --git a/compose/foundation/foundation-layout/api/current.txt b/compose/foundation/foundation-layout/api/current.txt
index 55ab96d..2f822aa 100644
--- a/compose/foundation/foundation-layout/api/current.txt
+++ b/compose/foundation/foundation-layout/api/current.txt
@@ -115,6 +115,7 @@
   }
 
   @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface FlowColumnScope extends androidx.compose.foundation.layout.ColumnScope {
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.ui.Modifier fillMaxColumnWidth(androidx.compose.ui.Modifier, optional @FloatRange(from=0.0, to=1.0) float fraction);
   }
 
   public final class FlowLayoutKt {
@@ -123,6 +124,7 @@
   }
 
   @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface FlowRowScope extends androidx.compose.foundation.layout.RowScope {
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.ui.Modifier fillMaxRowHeight(androidx.compose.ui.Modifier, optional @FloatRange(from=0.0, to=1.0) float fraction);
   }
 
   public final class IntrinsicKt {
diff --git a/compose/foundation/foundation-layout/api/restricted_current.txt b/compose/foundation/foundation-layout/api/restricted_current.txt
index c0844d9..cf9513b 100644
--- a/compose/foundation/foundation-layout/api/restricted_current.txt
+++ b/compose/foundation/foundation-layout/api/restricted_current.txt
@@ -118,6 +118,7 @@
   }
 
   @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface FlowColumnScope extends androidx.compose.foundation.layout.ColumnScope {
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.ui.Modifier fillMaxColumnWidth(androidx.compose.ui.Modifier, optional @FloatRange(from=0.0, to=1.0) float fraction);
   }
 
   public final class FlowLayoutKt {
@@ -128,6 +129,7 @@
   }
 
   @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Immutable public interface FlowRowScope extends androidx.compose.foundation.layout.RowScope {
+    method @SuppressCompatibility @androidx.compose.foundation.layout.ExperimentalLayoutApi public androidx.compose.ui.Modifier fillMaxRowHeight(androidx.compose.ui.Modifier, optional @FloatRange(from=0.0, to=1.0) float fraction);
   }
 
   public final class IntrinsicKt {
diff --git a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/SimpleFlowColumnDemo.kt b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/SimpleFlowColumnDemo.kt
index 3afa90f..eb0867f 100644
--- a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/SimpleFlowColumnDemo.kt
+++ b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/SimpleFlowColumnDemo.kt
@@ -20,13 +20,18 @@
 import androidx.compose.foundation.layout.ExperimentalLayoutApi
 import androidx.compose.foundation.layout.samples.SimpleFlowColumn
 import androidx.compose.foundation.layout.samples.SimpleFlowColumnWithWeights
+import androidx.compose.foundation.layout.samples.SimpleFlowColumn_EqualWidth
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
 
 @OptIn(ExperimentalLayoutApi::class)
 @Composable
 fun SimpleFlowColumnDemo() {
-    Column() {
+    Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
         SimpleFlowColumn()
         SimpleFlowColumnWithWeights()
+        SimpleFlowColumn_EqualWidth()
     }
 }
diff --git a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/SimpleFlowRowDemo.kt b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/SimpleFlowRowDemo.kt
index 447fc06..abc3b38 100644
--- a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/SimpleFlowRowDemo.kt
+++ b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/SimpleFlowRowDemo.kt
@@ -20,13 +20,18 @@
 import androidx.compose.foundation.layout.ExperimentalLayoutApi
 import androidx.compose.foundation.layout.samples.SimpleFlowRow
 import androidx.compose.foundation.layout.samples.SimpleFlowRowWithWeights
+import androidx.compose.foundation.layout.samples.SimpleFlowRow_EqualHeight
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
 
 @OptIn(ExperimentalLayoutApi::class)
 @Composable
 fun SimpleFlowRowDemo() {
-    Column {
+    Column(Modifier.verticalScroll(rememberScrollState())) {
         SimpleFlowRow()
         SimpleFlowRowWithWeights()
+        SimpleFlowRow_EqualHeight()
     }
 }
diff --git a/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/FlowColumnSample.kt b/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/FlowColumnSample.kt
index b5472254..cce4c34 100644
--- a/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/FlowColumnSample.kt
+++ b/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/FlowColumnSample.kt
@@ -30,6 +30,7 @@
 import androidx.compose.foundation.layout.requiredHeight
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentWidth
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
@@ -44,6 +45,7 @@
 fun SimpleFlowColumn() {
     FlowColumn(
         Modifier
+            .padding(20.dp)
             .fillMaxWidth()
             .wrapContentHeight(align = Alignment.Top)
             .requiredHeight(200.dp)
@@ -73,6 +75,7 @@
 fun SimpleFlowColumnWithWeights() {
     FlowColumn(
         Modifier
+            .padding(20.dp)
             .fillMaxWidth()
             .wrapContentHeight(align = Alignment.Top)
             .requiredHeight(200.dp)
@@ -94,3 +97,34 @@
         }
     }
 }
+
+@OptIn(ExperimentalLayoutApi::class)
+@Sampled
+@Composable
+fun SimpleFlowColumn_EqualWidth() {
+    FlowColumn(
+        Modifier
+            .padding(20.dp)
+            .wrapContentHeight(align = Alignment.Top)
+            .wrapContentWidth(align = Alignment.Start),
+        horizontalArrangement = Arrangement.spacedBy(10.dp),
+        verticalArrangement = Arrangement.spacedBy(20.dp),
+        maxItemsInEachColumn = 3,
+    ) {
+        repeat(9) {
+            Box(
+                Modifier
+                    .height(100.dp)
+                    .fillMaxColumnWidth(1f)
+                    .background(Color.Green)
+            ) {
+                val text = generateRandomString(IntRange(1, 5).random())
+                Text(
+                    text = text,
+                    fontSize = 18.sp,
+                    modifier = Modifier.padding(3.dp)
+                )
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/FlowRowSample.kt b/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/FlowRowSample.kt
index 7af3c27..3529ff8 100644
--- a/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/FlowRowSample.kt
+++ b/compose/foundation/foundation-layout/samples/src/main/java/androidx/compose/foundation/layout/samples/FlowRowSample.kt
@@ -34,6 +34,7 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
+import kotlin.random.Random
 
 @OptIn(ExperimentalLayoutApi::class)
 @Sampled
@@ -42,6 +43,7 @@
     FlowRow(
         Modifier
             .fillMaxWidth(1f)
+            .padding(20.dp)
             .wrapContentHeight(align = Alignment.Top),
         horizontalArrangement = Arrangement.spacedBy(10.dp),
         verticalArrangement = Arrangement.spacedBy(20.dp),
@@ -68,6 +70,7 @@
     FlowRow(
         Modifier
             .wrapContentHeight()
+            .padding(20.dp)
             .fillMaxWidth(1f),
         horizontalArrangement = Arrangement.spacedBy(10.dp),
         verticalArrangement = Arrangement.spacedBy(20.dp),
@@ -85,3 +88,47 @@
         }
     }
 }
+
+@OptIn(ExperimentalLayoutApi::class)
+@Sampled
+@Composable
+fun SimpleFlowRow_EqualHeight() {
+    FlowRow(
+        Modifier
+            .fillMaxWidth(1f)
+            .padding(20.dp)
+            .wrapContentHeight(align = Alignment.Top),
+        horizontalArrangement = Arrangement.spacedBy(10.dp),
+        verticalArrangement = Arrangement.spacedBy(20.dp),
+        maxItemsInEachRow = 3,
+    ) {
+        repeat(9) {
+            Box(
+                Modifier
+                    .width(100.dp)
+                    .background(Color.Green)
+                    .fillMaxRowHeight(1f)
+            ) {
+                val text = generateRandomString(IntRange(10, 50).random())
+                Text(
+                    text = text,
+                    fontSize = 18.sp,
+                    modifier = Modifier.padding(3.dp)
+                )
+            }
+        }
+    }
+}
+
+fun generateRandomString(length: Int): String {
+    val charPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9')
+    val random = Random.Default
+
+    val randomString = StringBuilder(length)
+    repeat(length) {
+        val randomIndex = random.nextInt(0, charPool.size)
+        val randomChar = charPool[randomIndex]
+        randomString.append(randomChar)
+    }
+    return randomString.toString()
+}
diff --git a/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/FlowRowColumnTest.kt b/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/FlowRowColumnTest.kt
index 3eeedac..1afc792 100644
--- a/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/FlowRowColumnTest.kt
+++ b/compose/foundation/foundation-layout/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/layout/FlowRowColumnTest.kt
@@ -16,21 +16,25 @@
 
 package androidx.compose.foundation.layout
 
+import androidx.compose.foundation.background
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.onPlaced
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.layout.positionInParent
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth
 import kotlin.math.roundToInt
+import kotlin.random.Random
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -320,6 +324,295 @@
     }
 
     @Test
+    fun testFlowRow_equalHeight() {
+        val listOfHeights = mutableListOf<Int>()
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                FlowRow(
+                    Modifier
+                        .fillMaxWidth(1f)
+                        .padding(20.dp)
+                        .wrapContentHeight(align = Alignment.Top),
+                    horizontalArrangement = Arrangement.spacedBy(10.dp),
+                    verticalArrangement = Arrangement.spacedBy(20.dp),
+                    maxItemsInEachRow = 3,
+                ) {
+                    repeat(9) {
+                        Box(
+                            Modifier
+                                .onSizeChanged {
+                                    listOfHeights.add(it.height)
+                                }
+                                .width(100.dp)
+                                .background(Color.Green)
+                                .fillMaxRowHeight()
+                        ) {
+                            val height = it * Random.Default.nextInt(0, 200)
+                            Box(modifier = Modifier.height(height.dp))
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(listOfHeights[0]).isEqualTo(listOfHeights[1])
+        Truth.assertThat(listOfHeights[1]).isEqualTo(listOfHeights[2])
+        Truth.assertThat(listOfHeights[2]).isNotEqualTo(listOfHeights[3])
+        Truth.assertThat(listOfHeights[3]).isEqualTo(listOfHeights[4])
+        Truth.assertThat(listOfHeights[4]).isEqualTo(listOfHeights[5])
+        Truth.assertThat(listOfHeights[5]).isNotEqualTo(listOfHeights[6])
+        Truth.assertThat(listOfHeights[6]).isEqualTo(listOfHeights[7])
+        Truth.assertThat(listOfHeights[7]).isEqualTo(listOfHeights[8])
+    }
+
+    @Test
+    fun testFlowRow_equalHeight_worksWithWeight() {
+        val listOfHeights = mutableListOf<Int>()
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                FlowRow(
+                    Modifier
+                        .fillMaxWidth(1f)
+                        .padding(20.dp)
+                        .wrapContentHeight(align = Alignment.Top),
+                    horizontalArrangement = Arrangement.spacedBy(10.dp),
+                    verticalArrangement = Arrangement.spacedBy(20.dp),
+                    maxItemsInEachRow = 3,
+                ) {
+                    repeat(9) {
+                        Box(
+                            Modifier
+                                .onSizeChanged {
+                                    listOfHeights.add(it.height)
+                                }
+                                .width(100.dp)
+                                .weight(1f, true)
+                                .background(Color.Green)
+                                .fillMaxRowHeight()
+                        ) {
+                            val height = it * Random.Default.nextInt(0, 200)
+                            Box(modifier = Modifier.height(height.dp))
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(listOfHeights[0]).isEqualTo(listOfHeights[1])
+        Truth.assertThat(listOfHeights[1]).isEqualTo(listOfHeights[2])
+        Truth.assertThat(listOfHeights[2]).isNotEqualTo(listOfHeights[3])
+        Truth.assertThat(listOfHeights[3]).isEqualTo(listOfHeights[4])
+        Truth.assertThat(listOfHeights[4]).isEqualTo(listOfHeights[5])
+        Truth.assertThat(listOfHeights[5]).isNotEqualTo(listOfHeights[6])
+        Truth.assertThat(listOfHeights[6]).isEqualTo(listOfHeights[7])
+        Truth.assertThat(listOfHeights[7]).isEqualTo(listOfHeights[8])
+    }
+
+    @Test
+    fun testFlowRow_equalHeight_WithFraction() {
+        val listOfHeights = mutableListOf<Int>()
+
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                with(LocalDensity.current) {
+                    FlowRow(
+                        Modifier
+                            .fillMaxWidth(1f)
+                            .padding(20.dp)
+                            .wrapContentHeight(align = Alignment.Top),
+                        horizontalArrangement = Arrangement.spacedBy(10.dp),
+                        verticalArrangement = Arrangement.spacedBy(20.dp),
+                        maxItemsInEachRow = 3,
+                    ) {
+                        repeat(9) {
+                            Box(
+                                Modifier
+                                    .onSizeChanged {
+                                        listOfHeights.add(it.height)
+                                    }
+                                    .width(100.dp)
+                                    .background(Color.Green)
+                                    .run {
+                                        if (it == 0 || it == 3 || it == 6) {
+                                            fillMaxRowHeight(0.5f)
+                                        } else {
+                                            this
+                                        }
+                                    }
+                            ) {
+                                val height = it * 400
+                                Box(modifier = Modifier.height(height.dp))
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(listOfHeights[0]).isEqualTo((.5 * listOfHeights[2]).roundToInt())
+        Truth.assertThat(listOfHeights[1]).isNotEqualTo(listOfHeights[2])
+        Truth.assertThat(listOfHeights[2]).isEqualTo(800)
+        Truth.assertThat(listOfHeights[2]).isNotEqualTo(listOfHeights[3])
+        Truth.assertThat(listOfHeights[3]).isEqualTo((.5 * listOfHeights[5]).roundToInt())
+        Truth.assertThat(listOfHeights[4]).isNotEqualTo(listOfHeights[5])
+        Truth.assertThat(listOfHeights[5]).isEqualTo(2000)
+        Truth.assertThat(listOfHeights[5]).isNotEqualTo(listOfHeights[6])
+        Truth.assertThat(listOfHeights[6]).isEqualTo((.5 * listOfHeights[8]).roundToInt())
+        Truth.assertThat(listOfHeights[7]).isNotEqualTo(listOfHeights[8])
+        Truth.assertThat(listOfHeights[8]).isEqualTo(3200)
+    }
+
+    @Test
+    fun testFlowColumn_equalWidth() {
+        val listOfWidths = mutableListOf<Int>()
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                FlowColumn(
+                    Modifier
+                        .fillMaxWidth(1f)
+                        .padding(20.dp)
+                        .wrapContentHeight(align = Alignment.Top),
+                    horizontalArrangement = Arrangement.spacedBy(20.dp),
+                    verticalArrangement = Arrangement.spacedBy(10.dp),
+                    maxItemsInEachColumn = 3,
+                ) {
+                    repeat(9) {
+                        Box(
+                            Modifier
+                                .onSizeChanged {
+                                    listOfWidths.add(it.width)
+                                }
+                                .height(100.dp)
+                                .background(Color.Green)
+                                .fillMaxColumnWidth()
+                        ) {
+                            val width = it * Random.Default.nextInt(0, 500)
+                            Box(modifier = Modifier.width(width.dp))
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(listOfWidths[0]).isEqualTo(listOfWidths[1])
+        Truth.assertThat(listOfWidths[1]).isEqualTo(listOfWidths[2])
+        Truth.assertThat(listOfWidths[2]).isNotEqualTo(listOfWidths[3])
+        Truth.assertThat(listOfWidths[3]).isEqualTo(listOfWidths[4])
+        Truth.assertThat(listOfWidths[4]).isEqualTo(listOfWidths[5])
+        Truth.assertThat(listOfWidths[5]).isNotEqualTo(listOfWidths[6])
+        Truth.assertThat(listOfWidths[6]).isEqualTo(listOfWidths[7])
+        Truth.assertThat(listOfWidths[7]).isEqualTo(listOfWidths[8])
+    }
+
+    @Test
+    fun testFlowColumn_equalWidth_worksWithWeight() {
+        val listOfWidths = mutableListOf<Int>()
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                FlowColumn(
+                    Modifier
+                        .fillMaxHeight(1f)
+                        .padding(20.dp),
+                    horizontalArrangement = Arrangement.spacedBy(20.dp),
+                    verticalArrangement = Arrangement.spacedBy(10.dp),
+                    maxItemsInEachColumn = 3,
+                ) {
+                    repeat(9) {
+                        Box(
+                            Modifier
+                                .onSizeChanged {
+                                    listOfWidths.add(it.width)
+                                }
+                                .height(100.dp)
+                                .weight(1f, true)
+                                .background(Color.Green)
+                                .fillMaxColumnWidth()
+                        ) {
+                            val width = it * Random.Default.nextInt(0, 500)
+                            Box(modifier = Modifier.width(width.dp))
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(listOfWidths[0]).isEqualTo(listOfWidths[1])
+        Truth.assertThat(listOfWidths[1]).isEqualTo(listOfWidths[2])
+        Truth.assertThat(listOfWidths[2]).isNotEqualTo(listOfWidths[3])
+        Truth.assertThat(listOfWidths[3]).isEqualTo(listOfWidths[4])
+        Truth.assertThat(listOfWidths[4]).isEqualTo(listOfWidths[5])
+        Truth.assertThat(listOfWidths[5]).isNotEqualTo(listOfWidths[6])
+        Truth.assertThat(listOfWidths[6]).isEqualTo(listOfWidths[7])
+        Truth.assertThat(listOfWidths[7]).isEqualTo(listOfWidths[8])
+    }
+
+    @Test
+    fun testFlowColumn_equalWidth_fraction() {
+        val listOfWidths = mutableListOf<Int>()
+
+        rule.setContent {
+            CompositionLocalProvider(
+                LocalDensity provides NoOpDensity
+            ) {
+                FlowColumn(
+                    Modifier
+                        .fillMaxWidth(1f)
+                        .padding(20.dp)
+                        .wrapContentHeight(align = Alignment.Top),
+                    horizontalArrangement = Arrangement.spacedBy(20.dp),
+                    verticalArrangement = Arrangement.spacedBy(10.dp),
+                    maxItemsInEachColumn = 3,
+                ) {
+                    repeat(9) {
+                        Box(
+                            Modifier
+                                .onSizeChanged {
+                                    listOfWidths.add(it.width)
+                                }
+                                .height(100.dp)
+                                .background(Color.Green)
+                                .run {
+                                    if (it == 0 || it == 3 || it == 6) {
+                                        fillMaxColumnWidth(0.5f)
+                                    } else {
+                                        this
+                                    }
+                                }
+                        ) {
+                            val width = it * 400
+                            Box(modifier = Modifier.width(width.dp))
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(listOfWidths[0]).isEqualTo((.5 * listOfWidths[2]).roundToInt())
+        Truth.assertThat(listOfWidths[1]).isNotEqualTo(listOfWidths[2])
+        Truth.assertThat(listOfWidths[2]).isEqualTo(800)
+        Truth.assertThat(listOfWidths[2]).isNotEqualTo(listOfWidths[3])
+        Truth.assertThat(listOfWidths[3]).isEqualTo((.5 * listOfWidths[5]).roundToInt())
+        Truth.assertThat(listOfWidths[4]).isNotEqualTo(listOfWidths[5])
+        Truth.assertThat(listOfWidths[5]).isEqualTo(2000)
+        Truth.assertThat(listOfWidths[5]).isNotEqualTo(listOfWidths[6])
+        Truth.assertThat(listOfWidths[6]).isEqualTo((.5 * listOfWidths[8]).roundToInt())
+        Truth.assertThat(listOfWidths[7]).isNotEqualTo(listOfWidths[8])
+        Truth.assertThat(listOfWidths[8]).isEqualTo(3200)
+    }
+
+    @Test
     fun testFlowColumn_staysInOneRow() {
         var width = 0
 
@@ -2539,3 +2832,8 @@
         Truth.assertThat(height).isEqualTo(200)
     }
 }
+
+private val NoOpDensity = object : Density {
+    override val density = 1f
+    override val fontScale = 1f
+}
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt
index e091b8b..3993165 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt
@@ -1,5 +1,6 @@
 package androidx.compose.foundation.layout
 
+import androidx.annotation.FloatRange
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.collection.MutableVector
@@ -15,7 +16,11 @@
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.MeasureScope
 import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.ParentDataModifierNode
+import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.constrainHeight
 import androidx.compose.ui.unit.constrainWidth
@@ -132,7 +137,23 @@
 @LayoutScopeMarker
 @Immutable
 @ExperimentalLayoutApi
-interface FlowRowScope : RowScope
+interface FlowRowScope : RowScope {
+    /**
+     * Have the item fill (possibly only partially) the max height of the tallest item in the
+     * row it was placed in, within the [FlowRow].
+     *
+     * @param fraction The fraction of the max height of the tallest item
+     * between `0` and `1`, inclusive.
+     *
+     * Example usage:
+     * @sample androidx.compose.foundation.layout.samples.SimpleFlowRow_EqualHeight
+     */
+    @ExperimentalLayoutApi
+    fun Modifier.fillMaxRowHeight(
+        @FloatRange(from = 0.0, to = 1.0)
+        fraction: Float = 1f,
+    ): Modifier
+}
 
 /**
  * Scope for the children of [FlowColumn].
@@ -140,13 +161,93 @@
 @LayoutScopeMarker
 @Immutable
 @ExperimentalLayoutApi
-interface FlowColumnScope : ColumnScope
+interface FlowColumnScope : ColumnScope {
+    /**
+     * Have the item fill (possibly only partially) the max width of the tallest item in the
+     * column it was placed in, within the [FlowColumn].
+     *
+     * @param fraction The fraction of the max width of the tallest item
+     * between `0` and `1`, inclusive.
+     *
+     * Example usage:
+     * @sample androidx.compose.foundation.layout.samples.SimpleFlowColumn_EqualWidth
+     */
+    @ExperimentalLayoutApi
+    fun Modifier.fillMaxColumnWidth(
+        @FloatRange(from = 0.0, to = 1.0)
+        fraction: Float = 1f,
+    ): Modifier
+}
 
 @OptIn(ExperimentalLayoutApi::class)
-internal object FlowRowScopeInstance : RowScope by RowScopeInstance, FlowRowScope
+internal object FlowRowScopeInstance : RowScope by RowScopeInstance, FlowRowScope {
+    override fun Modifier.fillMaxRowHeight(fraction: Float): Modifier {
+        require(fraction > 0.0) { "invalid fraction $fraction; must be greater than zero" }
+        require(fraction <= 1.0) { "invalid fraction $fraction; must not be greater than 1.0" }
+        return this.then(
+            FillCrossAxisSizeElement(
+                fraction = fraction,
+            )
+        )
+    }
+}
 
 @OptIn(ExperimentalLayoutApi::class)
-internal object FlowColumnScopeInstance : ColumnScope by ColumnScopeInstance, FlowColumnScope
+internal object FlowColumnScopeInstance : ColumnScope by ColumnScopeInstance, FlowColumnScope {
+    override fun Modifier.fillMaxColumnWidth(fraction: Float): Modifier {
+        require(fraction > 0.0) { "invalid fraction $fraction; must be greater than zero" }
+        require(fraction <= 1.0) { "invalid fraction $fraction; must not be greater than 1.0" }
+        return this.then(
+            FillCrossAxisSizeElement(
+                fraction = fraction,
+            )
+        )
+    }
+}
+
+internal data class FlowLayoutData(
+    var fillCrossAxisFraction: Float
+)
+
+internal class FillCrossAxisSizeNode(
+    var fraction: Float,
+) : ParentDataModifierNode, Modifier.Node() {
+    override fun Density.modifyParentData(parentData: Any?) =
+        ((parentData as? RowColumnParentData) ?: RowColumnParentData()).also {
+            it.flowLayoutData = it.flowLayoutData ?: FlowLayoutData(fraction)
+            it.flowLayoutData!!.fillCrossAxisFraction = fraction
+        }
+}
+
+internal class FillCrossAxisSizeElement(
+    val fraction: Float
+) : ModifierNodeElement<FillCrossAxisSizeNode>() {
+    override fun create(): FillCrossAxisSizeNode {
+        return FillCrossAxisSizeNode(fraction)
+    }
+
+    override fun update(node: FillCrossAxisSizeNode) {
+        node.fraction = fraction
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        name = "fraction"
+        value = fraction
+        properties["fraction"] = fraction
+    }
+
+    override fun hashCode(): Int {
+        var result = fraction.hashCode()
+        result *= 31
+        return result
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        val otherModifier = other as? FillCrossAxisSizeNode ?: return false
+        return fraction == otherModifier.fraction
+    }
+}
 
 @PublishedApi
 @Composable
@@ -620,48 +721,58 @@
         crossAxisMax
     )
     // nextSize of the list, pre-calculated
-    var nextSize: Int? = measurables.getOrNull(0)?.measureAndCache(
+    var nextSize: Pair<Int, Int>? = measurables.getOrNull(0)?.measureAndCache(
         subsetConstraints, orientation
     ) { placeable ->
         placeables[0] = placeable
     }
+    var nextMainAxisSize: Int? = nextSize?.first
+    var nextCrossAxisSize: Int? = nextSize?.second
 
     var startBreakLineIndex = 0
     val endBreakLineList = arrayOfNulls<Int>(measurables.size)
+    val crossAxisSizes = arrayOfNulls<Int>(measurables.size)
     var endBreakLineIndex = 0
 
     var leftOver = mainAxisMax
     // figure out the mainAxisTotalSize which will be minMainAxis when measuring the row/column
     var mainAxisTotalSize = mainAxisMin
     var currentLineMainAxisSize = 0
+    var currentLineCrossAxisSize = 0
     for (index in measurables.indices) {
-        val itemMainAxisSize = nextSize!!
+        val itemMainAxisSize = nextMainAxisSize!!
+        val itemCrossAxisSize = nextCrossAxisSize!!
         currentLineMainAxisSize += itemMainAxisSize
+        currentLineCrossAxisSize = maxOf(currentLineCrossAxisSize, itemCrossAxisSize)
         leftOver -= itemMainAxisSize
         nextSize = measurables.getOrNull(index + 1)?.measureAndCache(
             subsetConstraints, orientation
         ) { placeable ->
             placeables[index + 1] = placeable
-        }?.plus(spacing)
+        }
+        nextMainAxisSize = nextSize?.first?.plus(spacing)
+        nextCrossAxisSize = nextSize?.second ?: 0
         if (index + 1 >= measurables.size ||
             (index + 1) - startBreakLineIndex >= maxItemsInMainAxis ||
-            leftOver - (nextSize ?: 0) < 0
+            leftOver - (nextMainAxisSize ?: 0) < 0
         ) {
             mainAxisTotalSize = maxOf(mainAxisTotalSize, currentLineMainAxisSize)
             mainAxisTotalSize = minOf(mainAxisTotalSize, mainAxisMax)
-            currentLineMainAxisSize = 0
-            leftOver = mainAxisMax
             startBreakLineIndex = index + 1
             endBreakLineList[endBreakLineIndex] = index + 1
+            crossAxisSizes[endBreakLineIndex] = currentLineCrossAxisSize
             endBreakLineIndex++
+            currentLineMainAxisSize = 0
+            currentLineCrossAxisSize = 0
+            leftOver = mainAxisMax
             // only add spacing for next items in the row or column, not the starting indexes
-            nextSize = nextSize?.minus(spacing)
+            nextMainAxisSize = nextMainAxisSize?.minus(spacing)
         }
     }
 
     val subsetBoxConstraints = subsetConstraints.copy(
         mainAxisMin = mainAxisTotalSize
-    ).toBoxConstraints(orientation)
+    )
 
     startBreakLineIndex = 0
     var crossAxisTotalSize = 0
@@ -669,9 +780,12 @@
     endBreakLineIndex = 0
     var endIndex = endBreakLineList.getOrNull(endBreakLineIndex)
     while (endIndex != null) {
+        var crossAxisSize = crossAxisSizes[endBreakLineIndex]
         val result = measureHelper.measureWithoutPlacing(
             this,
-            subsetBoxConstraints,
+            subsetBoxConstraints.copy(
+                crossAxisMax = crossAxisSize!!
+            ).toBoxConstraints(orientation),
             startBreakLineIndex,
             endIndex
         )
@@ -726,17 +840,24 @@
     constraints: OrientationIndependentConstraints,
     orientation: LayoutOrientation,
     storePlaceable: (Placeable?) -> Unit
-): Int {
-    val itemSize: Int = if (rowColumnParentData.weight == 0f) {
+): Pair<Int, Int> {
+    val itemSize: Pair<Int, Int> = if (
+        rowColumnParentData.weight == 0f &&
+        rowColumnParentData?.flowLayoutData?.fillCrossAxisFraction == 0f
+    ) {
         // fixed sizes: measure once
         val placeable = measure(
             constraints.copy(
                 mainAxisMin = 0,
             ).toBoxConstraints(orientation)
         ).also(storePlaceable)
-        placeable.mainAxisSize(orientation)
+        val mainAxis = placeable.mainAxisSize(orientation)
+        val crossAxis = placeable.crossAxisSize(orientation)
+        Pair(mainAxis, crossAxis)
     } else {
-        mainAxisMin(orientation, Constraints.Infinity)
+        val mainAxis = mainAxisMin(orientation, Constraints.Infinity)
+        val crossAxis = crossAxisMin(orientation, mainAxis)
+        Pair(mainAxis, crossAxis)
     }
     return itemSize
 }
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
index 82150c7..cf7d6d1 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
@@ -830,7 +830,8 @@
 internal data class RowColumnParentData(
     var weight: Float = 0f,
     var fill: Boolean = true,
-    var crossAxisAlignment: CrossAxisAlignment? = null
+    var crossAxisAlignment: CrossAxisAlignment? = null,
+    var flowLayoutData: FlowLayoutData? = null,
 )
 
 /**
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurementHelper.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurementHelper.kt
index 0b54663..5c7d4d4 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurementHelper.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurementHelper.kt
@@ -109,6 +109,10 @@
                 ++weightChildrenCount
             } else {
                 val mainAxisMax = constraints.mainAxisMax
+                val crossAxisMax = constraints.crossAxisMax
+                val crossAxisDesiredSize = if (crossAxisMax == Constraints.Infinity) 0 else ((
+                    parentData?.flowLayoutData?.fillCrossAxisFraction ?: 0f
+                    ) * crossAxisMax).toInt()
                 val placeable = placeables[i] ?: child.measure(
                     // Ask for preferred main axis size.
                     constraints.copy(
@@ -118,7 +122,9 @@
                         } else {
                             (mainAxisMax - fixedSpace).coerceAtLeast(0).toInt()
                         },
-                        crossAxisMin = 0
+                        crossAxisMin = crossAxisDesiredSize,
+                        crossAxisMax = if (crossAxisDesiredSize != 0)
+                            crossAxisDesiredSize else constraints.crossAxisMax
                     ).toBoxConstraints(orientation)
                 )
                 spaceAfterLastNoWeight = min(
@@ -159,6 +165,11 @@
                     val child = measurables[i]
                     val parentData = rowColumnParentData[i]
                     val weight = parentData.weight
+                    val crossAxisMax = constraints.crossAxisMax
+                    val crossAxisDesiredSize = if (crossAxisMax == Constraints.Infinity) 0 else
+                        ((parentData?.flowLayoutData?.fillCrossAxisFraction ?: 0f) *
+                            crossAxisMax
+                    ).toInt()
                     check(weight > 0) { "All weights <= 0 should have placeables" }
                     // After the weightUnitSpace rounding, the total space going to be occupied
                     // can be smaller or larger than remainingToTarget. Here we distribute the
@@ -177,8 +188,9 @@
                                 0
                             },
                             childMainAxisSize,
-                            0,
-                            constraints.crossAxisMax
+                            crossAxisMin = crossAxisDesiredSize,
+                            crossAxisMax = if (crossAxisDesiredSize != 0)
+                                crossAxisDesiredSize else constraints.crossAxisMax
                         ).toBoxConstraints(orientation)
                     )
                     weightedSpace += placeable.mainAxisSize()
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ClickableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ClickableTest.kt
index 4c72289..b7a613f 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ClickableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ClickableTest.kt
@@ -32,6 +32,7 @@
 import androidx.compose.foundation.layout.requiredWidth
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.text.BasicText
+import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -43,8 +44,12 @@
 import androidx.compose.ui.draw.clipToBounds
 import androidx.compose.ui.focus.FocusManager
 import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.FocusState
+import androidx.compose.ui.focus.focusProperties
 import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.focus.onFocusEvent
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.InputMode
 import androidx.compose.ui.input.InputMode.Companion.Keyboard
 import androidx.compose.ui.input.InputMode.Companion.Touch
 import androidx.compose.ui.input.InputModeManager
@@ -165,7 +170,9 @@
             Box {
                 BasicText(
                     "ClickableText",
-                    modifier = Modifier.testTag("myClickable").clickable(onClick = onClick)
+                    modifier = Modifier
+                        .testTag("myClickable")
+                        .clickable(onClick = onClick)
                 )
             }
         }
@@ -1153,6 +1160,178 @@
         }
     }
 
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    @LargeTest
+    fun noHover_whenDisabled() {
+        val interactionSource = MutableInteractionSource()
+
+        lateinit var scope: CoroutineScope
+        val enabled = mutableStateOf(true)
+
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            BasicText(
+            "ClickableText",
+                modifier = Modifier
+                    .testTag("myClickable")
+                    .clickable(
+                        enabled = enabled.value,
+                        onClick = {},
+                        interactionSource = interactionSource,
+                        indication = null
+                    )
+            )
+        }
+
+        val interactions = mutableListOf<Interaction>()
+
+        scope.launch {
+            interactionSource.interactions.collect { interactions.add(it) }
+        }
+
+        rule.runOnIdle {
+            assertThat(interactions).isEmpty()
+        }
+
+        rule.onNodeWithTag("myClickable")
+            .performMouseInput { enter(center) }
+
+        rule.runOnIdle {
+            assertThat(interactions).hasSize(1)
+            assertThat(interactions.first()).isInstanceOf(HoverInteraction.Enter::class.java)
+        }
+
+        rule.onNodeWithTag("myClickable")
+            .performMouseInput { exit(Offset(-1f, -1f)) }
+
+        rule.runOnIdle {
+            interactions.clear()
+            enabled.value = false
+        }
+
+        rule.onNodeWithTag("myClickable")
+            .performMouseInput { enter(center) }
+
+        rule.runOnIdle {
+            assertThat(interactions).isEmpty()
+        }
+
+        rule.onNodeWithTag("myClickable")
+            .performMouseInput { exit(Offset(-1f, -1f)) }
+
+        rule.runOnIdle {
+            enabled.value = true
+        }
+
+        rule.onNodeWithTag("myClickable")
+            .performMouseInput { enter(center) }
+
+        rule.runOnIdle {
+            assertThat(interactions).hasSize(1)
+            assertThat(interactions.first()).isInstanceOf(HoverInteraction.Enter::class.java)
+        }
+
+        rule.onNodeWithTag("myClickable")
+            .performMouseInput { exit(Offset(-1f, -1f)) }
+    }
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    @Test
+    fun noFocus_whenDisabled() {
+        val requester = FocusRequester()
+        // Force clickable to always be in non-touch mode, so it should be focusable
+        val keyboardMockManager = object : InputModeManager {
+            override val inputMode = Keyboard
+            override fun requestInputMode(inputMode: InputMode) = true
+        }
+
+        val enabled = mutableStateOf(true)
+        lateinit var focusState: FocusState
+
+        rule.setContent {
+            CompositionLocalProvider(LocalInputModeManager provides keyboardMockManager) {
+                Box {
+                    BasicText(
+                        "ClickableText",
+                        modifier = Modifier
+                            .testTag("myClickable")
+                            .focusRequester(requester)
+                            .onFocusEvent { focusState = it }
+                            .clickable(enabled = enabled.value) {}
+                    )
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            requester.requestFocus()
+            assertThat(focusState.isFocused).isTrue()
+        }
+
+        rule.runOnIdle {
+            enabled.value = false
+        }
+
+        rule.runOnIdle {
+            assertThat(focusState.isFocused).isFalse()
+            requester.requestFocus()
+            assertThat(focusState.isFocused).isFalse()
+        }
+    }
+
+    /**
+     * Test for b/269319898
+     */
+    @OptIn(ExperimentalComposeUiApi::class)
+    @Test
+    fun noFocusPropertiesSet_whenDisabled() {
+        val requester = FocusRequester()
+        // Force clickable to always be in non-touch mode, so it should be focusable
+        val keyboardMockManager = object : InputModeManager {
+            override val inputMode = Keyboard
+            override fun requestInputMode(inputMode: InputMode) = true
+        }
+
+        val enabled = mutableStateOf(true)
+        lateinit var focusState: FocusState
+
+        rule.setContent {
+            CompositionLocalProvider(LocalInputModeManager provides keyboardMockManager) {
+                Box(Modifier.clickable(enabled = enabled.value, onClick = {})) {
+                    Box(
+                        Modifier
+                            .size(10.dp)
+                            // If clickable is setting canFocus to true without a focus target, then
+                            // that would override this property
+                            .focusProperties { canFocus = false }
+                            .focusRequester(requester)
+                            .onFocusEvent { focusState = it }
+                            .focusable()
+                    )
+                }
+            }
+        }
+
+        // b/314129026 we can't read canFocus, so instead try and request focus and make sure
+        // that we are not focused
+        rule.runOnIdle {
+            // Clickable is enabled, it should correctly apply properties to its focus node
+            requester.requestFocus()
+            assertThat(focusState.isFocused).isFalse()
+        }
+
+        rule.runOnIdle {
+            enabled.value = false
+        }
+
+        rule.runOnIdle {
+            // Clickable is disabled, it should not apply properties down the tree
+            requester.requestFocus()
+            assertThat(focusState.isFocused).isFalse()
+        }
+    }
+
     @Test
     fun testInspectorValue_noIndicationOverload() {
         val onClick: () -> Unit = { }
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableTest.kt
index 0defe94..5e233f4 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/CombinedClickableTest.kt
@@ -29,7 +29,9 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.requiredHeight
 import androidx.compose.foundation.layout.requiredWidth
+import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.text.BasicText
+import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -41,8 +43,12 @@
 import androidx.compose.ui.draw.clipToBounds
 import androidx.compose.ui.focus.FocusManager
 import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.FocusState
+import androidx.compose.ui.focus.focusProperties
 import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.focus.onFocusEvent
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.InputMode
 import androidx.compose.ui.input.InputMode.Companion.Keyboard
 import androidx.compose.ui.input.InputMode.Companion.Touch
 import androidx.compose.ui.input.InputModeManager
@@ -1751,6 +1757,178 @@
         }
     }
 
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    @LargeTest
+    fun noHover_whenDisabled() {
+        val interactionSource = MutableInteractionSource()
+
+        lateinit var scope: CoroutineScope
+        val enabled = mutableStateOf(true)
+
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            BasicText(
+                "ClickableText",
+                modifier = Modifier
+                    .testTag("myClickable")
+                    .combinedClickable(
+                        enabled = enabled.value,
+                        onClick = {},
+                        interactionSource = interactionSource,
+                        indication = null
+                    )
+            )
+        }
+
+        val interactions = mutableListOf<Interaction>()
+
+        scope.launch {
+            interactionSource.interactions.collect { interactions.add(it) }
+        }
+
+        rule.runOnIdle {
+            assertThat(interactions).isEmpty()
+        }
+
+        rule.onNodeWithTag("myClickable")
+            .performMouseInput { enter(center) }
+
+        rule.runOnIdle {
+            assertThat(interactions).hasSize(1)
+            assertThat(interactions.first()).isInstanceOf(HoverInteraction.Enter::class.java)
+        }
+
+        rule.onNodeWithTag("myClickable")
+            .performMouseInput { exit(Offset(-1f, -1f)) }
+
+        rule.runOnIdle {
+            interactions.clear()
+            enabled.value = false
+        }
+
+        rule.onNodeWithTag("myClickable")
+            .performMouseInput { enter(center) }
+
+        rule.runOnIdle {
+            assertThat(interactions).isEmpty()
+        }
+
+        rule.onNodeWithTag("myClickable")
+            .performMouseInput { exit(Offset(-1f, -1f)) }
+
+        rule.runOnIdle {
+            enabled.value = true
+        }
+
+        rule.onNodeWithTag("myClickable")
+            .performMouseInput { enter(center) }
+
+        rule.runOnIdle {
+            assertThat(interactions).hasSize(1)
+            assertThat(interactions.first()).isInstanceOf(HoverInteraction.Enter::class.java)
+        }
+
+        rule.onNodeWithTag("myClickable")
+            .performMouseInput { exit(Offset(-1f, -1f)) }
+    }
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    @Test
+    fun noFocus_whenDisabled() {
+        val requester = FocusRequester()
+        // Force clickable to always be in non-touch mode, so it should be focusable
+        val keyboardMockManager = object : InputModeManager {
+            override val inputMode = Keyboard
+            override fun requestInputMode(inputMode: InputMode) = true
+        }
+
+        val enabled = mutableStateOf(true)
+        lateinit var focusState: FocusState
+
+        rule.setContent {
+            CompositionLocalProvider(LocalInputModeManager provides keyboardMockManager) {
+                Box {
+                    BasicText(
+                        "ClickableText",
+                        modifier = Modifier
+                            .testTag("myClickable")
+                            .focusRequester(requester)
+                            .onFocusEvent { focusState = it }
+                            .combinedClickable(enabled = enabled.value) {}
+                    )
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            requester.requestFocus()
+            assertThat(focusState.isFocused).isTrue()
+        }
+
+        rule.runOnIdle {
+            enabled.value = false
+        }
+
+        rule.runOnIdle {
+            assertThat(focusState.isFocused).isFalse()
+            requester.requestFocus()
+            assertThat(focusState.isFocused).isFalse()
+        }
+    }
+
+    /**
+     * Test for b/269319898
+     */
+    @OptIn(ExperimentalComposeUiApi::class)
+    @Test
+    fun noFocusPropertiesSet_whenDisabled() {
+        val requester = FocusRequester()
+        // Force clickable to always be in non-touch mode, so it should be focusable
+        val keyboardMockManager = object : InputModeManager {
+            override val inputMode = Keyboard
+            override fun requestInputMode(inputMode: InputMode) = true
+        }
+
+        val enabled = mutableStateOf(true)
+        lateinit var focusState: FocusState
+
+        rule.setContent {
+            CompositionLocalProvider(LocalInputModeManager provides keyboardMockManager) {
+                Box(Modifier.combinedClickable(enabled = enabled.value, onClick = {})) {
+                    Box(
+                        Modifier
+                            .size(10.dp)
+                            // If clickable is setting canFocus to true without a focus target, then
+                            // that would override this property
+                            .focusProperties { canFocus = false }
+                            .focusRequester(requester)
+                            .onFocusEvent { focusState = it }
+                            .focusable()
+                    )
+                }
+            }
+        }
+
+        // b/314129026 we can't read canFocus, so instead try and request focus and make sure
+        // that we are not focused
+        rule.runOnIdle {
+            // Clickable is enabled, it should correctly apply properties to its focus node
+            requester.requestFocus()
+            assertThat(focusState.isFocused).isFalse()
+        }
+
+        rule.runOnIdle {
+            enabled.value = false
+        }
+
+        rule.runOnIdle {
+            // Clickable is disabled, it should not apply properties down the tree
+            requester.requestFocus()
+            assertThat(focusState.isFocused).isFalse()
+        }
+    }
+
     @Test
     fun testInspectorValue_noIndicationOverload() {
         val onClick: () -> Unit = { }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
index 3af83ab..e9722775 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
@@ -26,6 +26,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
+import androidx.compose.ui.focus.focusTarget
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.key.Key
 import androidx.compose.ui.input.key.KeyEvent
@@ -73,6 +74,11 @@
  * If you need to support double click or long click alongside the single click, consider
  * using [combinedClickable].
  *
+ * ***Note*** Any removal operations on Android Views from `clickable` should wrap `onClick` in a
+ * `post { }` block to guarantee the event dispatch completes before executing the removal. (You
+ * do not need to do this when removing a composable because Compose guarantees it completes via the
+ * snapshot state system.)
+ *
  * @sample androidx.compose.foundation.samples.ClickableSample
  *
  * @param enabled Controls the enabled state. When `false`, [onClick], and this modifier will
@@ -150,9 +156,8 @@
 ) {
     Modifier
         .indication(interactionSource, indication)
-        .hoverable(enabled = enabled, interactionSource = interactionSource)
-        .focusableInNonTouchMode(enabled = enabled, interactionSource = interactionSource)
         .then(ClickableElement(interactionSource, enabled, onClickLabel, role, onClick))
+        .then(if (enabled) Modifier.focusTarget() else Modifier)
 }
 /**
  * Configure component to receive clicks, double clicks and long clicks via input or accessibility
@@ -273,8 +278,6 @@
 ) {
     Modifier
         .indication(interactionSource, indication)
-        .hoverable(enabled = enabled, interactionSource = interactionSource)
-        .focusableInNonTouchMode(enabled = enabled, interactionSource = interactionSource)
         .then(
             CombinedClickableElement(
                 interactionSource,
@@ -287,6 +290,7 @@
                 onDoubleClick
             )
         )
+        .then(if (enabled) Modifier.focusTarget() else Modifier)
 }
 
 private suspend fun PressGestureScope.handlePressInteraction(
@@ -740,6 +744,9 @@
 ) : DelegatingNode(), PointerInputModifierNode, KeyInputModifierNode {
     abstract val clickablePointerInputNode: AbstractClickablePointerInputNode
     abstract val clickableSemanticsNode: ClickableSemanticsNode
+    private val hoverableNode: HoverableNode = HoverableNode(interactionSource)
+    private val focusableInNonTouchMode: FocusableInNonTouchMode = FocusableInNonTouchMode()
+    private val focusableNode: FocusableNode = FocusableNode(interactionSource)
 
     class InteractionData {
         val currentKeyPressInteractions = mutableMapOf<Key, PressInteraction.Press>()
@@ -761,7 +768,14 @@
             this.interactionSource = interactionSource
         }
         if (this.enabled != enabled) {
-            if (!enabled) {
+            if (enabled) {
+                delegate(hoverableNode)
+                delegate(focusableInNonTouchMode)
+                delegate(focusableNode)
+            } else {
+                undelegate(hoverableNode)
+                undelegate(focusableInNonTouchMode)
+                undelegate(focusableNode)
                 disposeInteractionSource()
             }
             this.enabled = enabled
@@ -769,6 +783,16 @@
         this.onClickLabel = onClickLabel
         this.role = role
         this.onClick = onClick
+        hoverableNode.updateInteractionSource(interactionSource)
+        focusableNode.update(interactionSource)
+    }
+
+    override fun onAttach() {
+        if (enabled) {
+            delegate(hoverableNode)
+            delegate(focusableInNonTouchMode)
+            delegate(focusableNode)
+        }
     }
 
     override fun onDetach() {
@@ -792,10 +816,16 @@
         pass: PointerEventPass,
         bounds: IntSize
     ) {
+        if (hoverableNode.isAttached) {
+            hoverableNode.onPointerEvent(pointerEvent, pass, bounds)
+        }
         clickablePointerInputNode.onPointerEvent(pointerEvent, pass, bounds)
     }
 
     override fun onCancelPointerInput() {
+        if (hoverableNode.isAttached) {
+            hoverableNode.onCancelPointerInput()
+        }
         clickablePointerInputNode.onCancelPointerInput()
     }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
index 88c8618..012ac24 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
@@ -130,7 +130,7 @@
 },
     factory = {
         Modifier
-            .then(FocusableInNonTouchModeElement)
+            .then(if (enabled) FocusableInNonTouchModeElement else Modifier)
             .focusable(enabled, interactionSource)
     })
 
@@ -149,7 +149,7 @@
         }
     }
 
-private class FocusableInNonTouchMode : Modifier.Node(), CompositionLocalConsumerModifierNode,
+internal class FocusableInNonTouchMode : Modifier.Node(), CompositionLocalConsumerModifierNode,
     FocusPropertiesModifierNode {
 
     private val inputModeManager: InputModeManager
@@ -193,7 +193,7 @@
 }
 
 @OptIn(ExperimentalFoundationApi::class)
-private class FocusableNode(
+internal class FocusableNode(
     interactionSource: MutableInteractionSource?
 ) : DelegatingNode(), FocusEventModifierNode, LayoutAwareModifierNode, SemanticsModifierNode,
     GlobalPositionAwareModifierNode {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Hoverable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Hoverable.kt
index cfc7267..9ae866f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Hoverable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Hoverable.kt
@@ -68,7 +68,7 @@
     }
 }
 
-private class HoverableNode(
+internal class HoverableNode(
     private var interactionSource: MutableInteractionSource
 ) : PointerInputModifierNode, Modifier.Node() {
     private var hoverInteraction: HoverInteraction.Enter? = null
diff --git a/compose/material3/material3-adaptive-navigation-suite/api/current.txt b/compose/material3/material3-adaptive-navigation-suite/api/current.txt
index db286e5..131dea0 100644
--- a/compose/material3/material3-adaptive-navigation-suite/api/current.txt
+++ b/compose/material3/material3-adaptive-navigation-suite/api/current.txt
@@ -56,9 +56,11 @@
     method public String getNavigationBar();
     method public String getNavigationDrawer();
     method public String getNavigationRail();
+    method public String getNone();
     property public final String NavigationBar;
     property public final String NavigationDrawer;
     property public final String NavigationRail;
+    property public final String None;
   }
 
 }
diff --git a/compose/material3/material3-adaptive-navigation-suite/api/restricted_current.txt b/compose/material3/material3-adaptive-navigation-suite/api/restricted_current.txt
index db286e5..131dea0 100644
--- a/compose/material3/material3-adaptive-navigation-suite/api/restricted_current.txt
+++ b/compose/material3/material3-adaptive-navigation-suite/api/restricted_current.txt
@@ -56,9 +56,11 @@
     method public String getNavigationBar();
     method public String getNavigationDrawer();
     method public String getNavigationRail();
+    method public String getNone();
     property public final String NavigationBar;
     property public final String NavigationDrawer;
     property public final String NavigationRail;
+    property public final String None;
   }
 
 }
diff --git a/compose/material3/material3-adaptive-navigation-suite/build.gradle b/compose/material3/material3-adaptive-navigation-suite/build.gradle
index 93430ff2..67b8c3b 100644
--- a/compose/material3/material3-adaptive-navigation-suite/build.gradle
+++ b/compose/material3/material3-adaptive-navigation-suite/build.gradle
@@ -59,8 +59,6 @@
             dependsOn(jvmMain)
             dependencies {
                 api("androidx.annotation:annotation:1.1.0")
-                implementation(project(":lifecycle:lifecycle-runtime-compose"))
-                implementation(project(":window:window"))
             }
         }
 
diff --git a/compose/material3/material3-adaptive-navigation-suite/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive-navigation-suite/NavigationSuiteTest.kt b/compose/material3/material3-adaptive-navigation-suite/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive-navigation-suite/NavigationSuiteTest.kt
new file mode 100644
index 0000000..3dffee7
--- /dev/null
+++ b/compose/material3/material3-adaptive-navigation-suite/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive-navigation-suite/NavigationSuiteTest.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3.adaptive.navigation.suite
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalMaterial3AdaptiveNavigationSuiteApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NavigationSuiteTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun navigationLayoutTypeTest_None() {
+        val layoutType = NavigationSuiteType.None
+
+        rule.setContent {
+            SampleNavigationSuite(layoutType = layoutType)
+        }
+
+        rule.onNodeWithTag("NavigationSuite").assertIsNotDisplayed()
+    }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveNavigationSuiteApi::class)
+@Composable
+private fun SampleNavigationSuite(
+    layoutType: NavigationSuiteType
+) {
+    NavigationSuite(
+        modifier = Modifier.testTag("NavigationSuite"),
+        layoutType = layoutType
+    ) {}
+}
diff --git a/compose/material3/material3-adaptive-navigation-suite/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation-suite/NavigationSuiteScaffold.kt b/compose/material3/material3-adaptive-navigation-suite/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation-suite/NavigationSuiteScaffold.kt
index 8686392..aa5fae8 100644
--- a/compose/material3/material3-adaptive-navigation-suite/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation-suite/NavigationSuiteScaffold.kt
+++ b/compose/material3/material3-adaptive-navigation-suite/src/commonMain/kotlin/androidx/compose/material3/adaptive/navigation-suite/NavigationSuiteScaffold.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
 import androidx.compose.material3.BadgedBox
 import androidx.compose.material3.DrawerDefaults
 import androidx.compose.material3.Icon
@@ -56,9 +57,8 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.util.fastForEach
-import androidx.compose.ui.util.fastMap
-import androidx.compose.ui.util.fastMaxOfOrNull
+import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.util.fastFirst
 
 /**
  * The Navigation Suite Scaffold wraps the provided content and places the adequate provided
@@ -129,52 +129,55 @@
         NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(WindowAdaptiveInfoDefault),
     content: @Composable () -> Unit = {}
 ) {
-    Layout(
-        contents = listOf(navigationSuite, content)
-    ) { (navigationMeasurables, contentMeasurables), constraints ->
-        val navigationPlaceables = navigationMeasurables.fastMap { it.measure(constraints) }
+    Layout({
+        // Wrap the navigation suite and content composables each in a Box to not propagate the
+        // parent's (Surface) min constraints to its children (see b/312664933).
+        Box(Modifier.layoutId(NavigationSuiteLayoutIdTag)) {
+            navigationSuite()
+        }
+        Box(Modifier.layoutId(ContentLayoutIdTag)) {
+            content()
+        }
+    }
+    ) { measurables, constraints ->
+        val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0)
+        // Find the navigation suite composable through it's layoutId tag
+        val navigationPlaceable =
+            measurables.fastFirst { it.layoutId == NavigationSuiteLayoutIdTag }
+                .measure(looseConstraints)
         val isNavigationBar = layoutType == NavigationSuiteType.NavigationBar
         val layoutHeight = constraints.maxHeight
         val layoutWidth = constraints.maxWidth
-        val contentPlaceables = contentMeasurables.fastMap { it.measure(
-            if (isNavigationBar) {
-                constraints.copy(
-                    minHeight = layoutHeight - (navigationPlaceables.fastMaxOfOrNull { it.height }
-                        ?: 0),
-                    maxHeight = layoutHeight - (navigationPlaceables.fastMaxOfOrNull { it.height }
-                        ?: 0)
-                )
-            } else {
-                constraints.copy(
-                    minWidth = layoutWidth - (navigationPlaceables.fastMaxOfOrNull { it.width }
-                        ?: 0),
-                    maxWidth = layoutWidth - (navigationPlaceables.fastMaxOfOrNull { it.width }
-                        ?: 0)
-                )
-            }
-        ) }
+        // Find the content composable through it's layoutId tag
+        val contentPlaceable =
+            measurables.fastFirst { it.layoutId == ContentLayoutIdTag }.measure(
+                if (isNavigationBar) {
+                    constraints.copy(
+                        minHeight = layoutHeight - navigationPlaceable.height,
+                        maxHeight = layoutHeight - navigationPlaceable.height
+                    )
+                } else {
+                    constraints.copy(
+                        minWidth = layoutWidth - navigationPlaceable.width,
+                        maxWidth = layoutWidth - navigationPlaceable.width
+                    )
+                }
+            )
 
         layout(layoutWidth, layoutHeight) {
             if (isNavigationBar) {
                 // Place content above the navigation component.
-                contentPlaceables.fastForEach {
-                    it.placeRelative(0, 0)
-                }
+                contentPlaceable.placeRelative(0, 0)
                 // Place the navigation component at the bottom of the screen.
-                navigationPlaceables.fastForEach {
-                    it.placeRelative(
-                        0,
-                        layoutHeight - (navigationPlaceables.fastMaxOfOrNull { it.height } ?: 0))
-                }
+                navigationPlaceable.placeRelative(
+                    0,
+                    layoutHeight - (navigationPlaceable.height)
+                )
             } else {
                 // Place the navigation component at the start of the screen.
-                navigationPlaceables.fastForEach {
-                    it.placeRelative(0, 0)
-                }
+                navigationPlaceable.placeRelative(0, 0)
                 // Place content to the side of the navigation component.
-                contentPlaceables.fastForEach {
-                    it.placeRelative((navigationPlaceables.fastMaxOfOrNull { it.width } ?: 0), 0)
-                }
+                contentPlaceable.placeRelative((navigationPlaceable.width), 0)
             }
         }
     }
@@ -276,6 +279,8 @@
                 }
             }
         }
+
+        NavigationSuiteType.None -> { /* Do nothing. */ }
     }
 }
 
@@ -358,6 +363,12 @@
          * @see PermanentDrawerSheet
          */
         val NavigationDrawer = NavigationSuiteType(description = "NavigationDrawer")
+
+        /**
+         * A navigation suite type that instructs the [NavigationSuite] to not display any
+         * navigation components on the screen.
+         */
+        val None = NavigationSuiteType(description = "None")
     }
 }
 
@@ -563,3 +574,6 @@
         icon()
     }
 }
+
+private const val NavigationSuiteLayoutIdTag = "navigationSuite"
+private const val ContentLayoutIdTag = "content"
diff --git a/compose/material3/material3-adaptive/api/current.txt b/compose/material3/material3-adaptive/api/current.txt
index ed7a5d9..9183d40 100644
--- a/compose/material3/material3-adaptive/api/current.txt
+++ b/compose/material3/material3-adaptive/api/current.txt
@@ -44,17 +44,19 @@
     field public static final androidx.compose.material3.adaptive.ListDetailPaneScaffoldDefaults INSTANCE;
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public enum ListDetailPaneScaffoldRole {
-    method public static androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
-    method public static androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole[] values();
-    enum_constant public static final androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole Detail;
-    enum_constant public static final androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole Extra;
-    enum_constant public static final androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole List;
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ListDetailPaneScaffoldRole {
+    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getDetail();
+    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getExtra();
+    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getList();
+    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Detail;
+    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Extra;
+    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole List;
+    field public static final androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole INSTANCE;
   }
 
   public final class ListDetailPaneScaffold_androidKt {
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit> listPane, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.adaptive.ThreePaneScaffoldState scaffoldState, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit> detailPane);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ThreePaneScaffoldState calculateListDetailPaneScaffoldState(optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole currentPaneDestination);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ThreePaneScaffoldState calculateListDetailPaneScaffoldState(optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional androidx.compose.material3.adaptive.ThreePaneScaffoldRole currentPaneDestination);
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @kotlin.jvm.JvmInline public final value class PaneAdaptedValue {
@@ -116,17 +118,19 @@
     field public static final androidx.compose.material3.adaptive.SupportingPaneScaffoldDefaults INSTANCE;
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public enum SupportingPaneScaffoldRole {
-    method public static androidx.compose.material3.adaptive.SupportingPaneScaffoldRole valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
-    method public static androidx.compose.material3.adaptive.SupportingPaneScaffoldRole[] values();
-    enum_constant public static final androidx.compose.material3.adaptive.SupportingPaneScaffoldRole Extra;
-    enum_constant public static final androidx.compose.material3.adaptive.SupportingPaneScaffoldRole Main;
-    enum_constant public static final androidx.compose.material3.adaptive.SupportingPaneScaffoldRole Supporting;
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class SupportingPaneScaffoldRole {
+    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getExtra();
+    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getMain();
+    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getSupporting();
+    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Extra;
+    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Main;
+    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Supporting;
+    field public static final androidx.compose.material3.adaptive.SupportingPaneScaffoldRole INSTANCE;
   }
 
   public final class SupportingPaneScaffold_androidKt {
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.adaptive.ThreePaneScaffoldState scaffoldState, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit> mainPane);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ThreePaneScaffoldState calculateSupportingPaneScaffoldState(optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional androidx.compose.material3.adaptive.SupportingPaneScaffoldRole currentPaneDestination);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ThreePaneScaffoldState calculateSupportingPaneScaffoldState(optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional androidx.compose.material3.adaptive.ThreePaneScaffoldRole currentPaneDestination);
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ThreePaneScaffoldAdaptStrategies {
@@ -138,17 +142,17 @@
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void AnimatedPane(androidx.compose.material3.adaptive.ThreePaneScaffoldScope, androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit> content);
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public interface ThreePaneScaffoldNavigator<T> {
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public interface ThreePaneScaffoldNavigator {
     method public boolean canNavigateBack(optional boolean scaffoldValueMustChange);
     method public androidx.compose.material3.adaptive.ThreePaneScaffoldState getScaffoldState();
     method public boolean navigateBack(optional boolean popUntilScaffoldValueChange);
-    method public void navigateTo(T pane);
+    method public void navigateTo(androidx.compose.material3.adaptive.ThreePaneScaffoldRole pane);
     property public abstract androidx.compose.material3.adaptive.ThreePaneScaffoldState scaffoldState;
   }
 
   public final class ThreePaneScaffoldNavigator_androidKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ThreePaneScaffoldNavigator<androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole> rememberListDetailPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional java.util.List<? extends androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole> initialDestinationHistory);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ThreePaneScaffoldNavigator<androidx.compose.material3.adaptive.SupportingPaneScaffoldRole> rememberSupportingPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional java.util.List<? extends androidx.compose.material3.adaptive.SupportingPaneScaffoldRole> initialDestinationHistory);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ThreePaneScaffoldNavigator rememberListDetailPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional java.util.List<? extends androidx.compose.material3.adaptive.ThreePaneScaffoldRole> initialDestinationHistory);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ThreePaneScaffoldNavigator rememberSupportingPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional java.util.List<? extends androidx.compose.material3.adaptive.ThreePaneScaffoldRole> initialDestinationHistory);
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public enum ThreePaneScaffoldRole {
@@ -191,7 +195,7 @@
   }
 
   public final class ThreePaneScaffoldValueKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.ThreePaneScaffoldValue calculateThreePaneScaffoldValue(int maxHorizontalPartitions, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional androidx.compose.material3.adaptive.ThreePaneScaffoldRole? currentFocus);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.ThreePaneScaffoldValue calculateThreePaneScaffoldValue(int maxHorizontalPartitions, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional androidx.compose.material3.adaptive.ThreePaneScaffoldRole? currentDestination);
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class WindowAdaptiveInfo {
diff --git a/compose/material3/material3-adaptive/api/restricted_current.txt b/compose/material3/material3-adaptive/api/restricted_current.txt
index ed7a5d9..9183d40 100644
--- a/compose/material3/material3-adaptive/api/restricted_current.txt
+++ b/compose/material3/material3-adaptive/api/restricted_current.txt
@@ -44,17 +44,19 @@
     field public static final androidx.compose.material3.adaptive.ListDetailPaneScaffoldDefaults INSTANCE;
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public enum ListDetailPaneScaffoldRole {
-    method public static androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
-    method public static androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole[] values();
-    enum_constant public static final androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole Detail;
-    enum_constant public static final androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole Extra;
-    enum_constant public static final androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole List;
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ListDetailPaneScaffoldRole {
+    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getDetail();
+    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getExtra();
+    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getList();
+    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Detail;
+    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Extra;
+    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole List;
+    field public static final androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole INSTANCE;
   }
 
   public final class ListDetailPaneScaffold_androidKt {
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit> listPane, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.adaptive.ThreePaneScaffoldState scaffoldState, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit> detailPane);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ThreePaneScaffoldState calculateListDetailPaneScaffoldState(optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole currentPaneDestination);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ThreePaneScaffoldState calculateListDetailPaneScaffoldState(optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional androidx.compose.material3.adaptive.ThreePaneScaffoldRole currentPaneDestination);
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @kotlin.jvm.JvmInline public final value class PaneAdaptedValue {
@@ -116,17 +118,19 @@
     field public static final androidx.compose.material3.adaptive.SupportingPaneScaffoldDefaults INSTANCE;
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public enum SupportingPaneScaffoldRole {
-    method public static androidx.compose.material3.adaptive.SupportingPaneScaffoldRole valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
-    method public static androidx.compose.material3.adaptive.SupportingPaneScaffoldRole[] values();
-    enum_constant public static final androidx.compose.material3.adaptive.SupportingPaneScaffoldRole Extra;
-    enum_constant public static final androidx.compose.material3.adaptive.SupportingPaneScaffoldRole Main;
-    enum_constant public static final androidx.compose.material3.adaptive.SupportingPaneScaffoldRole Supporting;
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class SupportingPaneScaffoldRole {
+    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getExtra();
+    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getMain();
+    method public androidx.compose.material3.adaptive.ThreePaneScaffoldRole getSupporting();
+    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Extra;
+    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Main;
+    property public final androidx.compose.material3.adaptive.ThreePaneScaffoldRole Supporting;
+    field public static final androidx.compose.material3.adaptive.SupportingPaneScaffoldRole INSTANCE;
   }
 
   public final class SupportingPaneScaffold_androidKt {
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.adaptive.ThreePaneScaffoldState scaffoldState, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit> mainPane);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ThreePaneScaffoldState calculateSupportingPaneScaffoldState(optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional androidx.compose.material3.adaptive.SupportingPaneScaffoldRole currentPaneDestination);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ThreePaneScaffoldState calculateSupportingPaneScaffoldState(optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional androidx.compose.material3.adaptive.ThreePaneScaffoldRole currentPaneDestination);
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class ThreePaneScaffoldAdaptStrategies {
@@ -138,17 +142,17 @@
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void AnimatedPane(androidx.compose.material3.adaptive.ThreePaneScaffoldScope, androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.ThreePaneScaffoldScope,kotlin.Unit> content);
   }
 
-  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public interface ThreePaneScaffoldNavigator<T> {
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public interface ThreePaneScaffoldNavigator {
     method public boolean canNavigateBack(optional boolean scaffoldValueMustChange);
     method public androidx.compose.material3.adaptive.ThreePaneScaffoldState getScaffoldState();
     method public boolean navigateBack(optional boolean popUntilScaffoldValueChange);
-    method public void navigateTo(T pane);
+    method public void navigateTo(androidx.compose.material3.adaptive.ThreePaneScaffoldRole pane);
     property public abstract androidx.compose.material3.adaptive.ThreePaneScaffoldState scaffoldState;
   }
 
   public final class ThreePaneScaffoldNavigator_androidKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ThreePaneScaffoldNavigator<androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole> rememberListDetailPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional java.util.List<? extends androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole> initialDestinationHistory);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ThreePaneScaffoldNavigator<androidx.compose.material3.adaptive.SupportingPaneScaffoldRole> rememberSupportingPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional java.util.List<? extends androidx.compose.material3.adaptive.SupportingPaneScaffoldRole> initialDestinationHistory);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ThreePaneScaffoldNavigator rememberListDetailPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional java.util.List<? extends androidx.compose.material3.adaptive.ThreePaneScaffoldRole> initialDestinationHistory);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.ThreePaneScaffoldNavigator rememberSupportingPaneScaffoldNavigator(optional androidx.compose.material3.adaptive.PaneScaffoldDirective scaffoldDirective, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional java.util.List<? extends androidx.compose.material3.adaptive.ThreePaneScaffoldRole> initialDestinationHistory);
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public enum ThreePaneScaffoldRole {
@@ -191,7 +195,7 @@
   }
 
   public final class ThreePaneScaffoldValueKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.ThreePaneScaffoldValue calculateThreePaneScaffoldValue(int maxHorizontalPartitions, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional androidx.compose.material3.adaptive.ThreePaneScaffoldRole? currentFocus);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public static androidx.compose.material3.adaptive.ThreePaneScaffoldValue calculateThreePaneScaffoldValue(int maxHorizontalPartitions, optional androidx.compose.material3.adaptive.ThreePaneScaffoldAdaptStrategies adaptStrategies, optional androidx.compose.material3.adaptive.ThreePaneScaffoldRole? currentDestination);
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public final class WindowAdaptiveInfo {
diff --git a/compose/material3/material3-adaptive/benchmark/build.gradle b/compose/material3/material3-adaptive/benchmark/build.gradle
deleted file mode 100644
index 48f6444..0000000
--- a/compose/material3/material3-adaptive/benchmark/build.gradle
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-plugins {
-    id("AndroidXPlugin")
-    id("com.android.library")
-    id("AndroidXComposePlugin")
-    id("org.jetbrains.kotlin.android")
-    id("androidx.benchmark")
-}
-
-dependencies {
-    androidTestImplementation(project(":compose:material3:material3-adaptive"))
-    androidTestImplementation(project(":benchmark:benchmark-junit4"))
-    androidTestImplementation(project(":compose:runtime:runtime"))
-    androidTestImplementation(project(":compose:benchmark-utils"))
-    androidTestImplementation(libs.testRules)
-    androidTestImplementation(libs.kotlinStdlib)
-    androidTestImplementation(libs.kotlinTestCommon)
-    androidTestImplementation(libs.junit)
-}
-
-android {
-    namespace "androidx.compose.material3.adaptive.benchmark"
-}
diff --git a/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/ListDetailPaneScaffoldBenchmark.kt b/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/ListDetailPaneScaffoldBenchmark.kt
deleted file mode 100644
index b2e7be5..0000000
--- a/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/ListDetailPaneScaffoldBenchmark.kt
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.material3.adaptive.benchmark
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.adaptive.AnimatedPane
-import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
-import androidx.compose.material3.adaptive.ListDetailPaneScaffold
-import androidx.compose.material3.adaptive.ListDetailPaneScaffoldRole
-import androidx.compose.material3.adaptive.calculateListDetailPaneScaffoldState
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.testutils.LayeredComposeTestCase
-import androidx.compose.testutils.ToggleableTestCase
-import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
-import androidx.compose.testutils.benchmark.benchmarkFirstCompose
-import androidx.compose.testutils.benchmark.benchmarkToFirstPixel
-import androidx.compose.testutils.benchmark.toggleStateBenchmarkComposeMeasureLayout
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.testTag
-import org.junit.Rule
-import org.junit.Test
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-class ListDetailPaneScaffoldBenchmark {
-    @get:Rule
-    val benchmarkRule = ComposeBenchmarkRule()
-
-    @Test
-    fun singlePane_firstPixel() {
-        benchmarkRule.benchmarkToFirstPixel {
-            ListDetailPaneScaffoldTestCase().apply {
-                currentScaffoldDirective = singlePaneDirective
-            }
-        }
-    }
-
-    @Test
-    fun dualPane_firstPixel() {
-        benchmarkRule.benchmarkToFirstPixel {
-            ListDetailPaneScaffoldTestCase().apply {
-                currentScaffoldDirective = dualPaneDirective
-            }
-        }
-    }
-
-    @Test
-    fun singlePane_firstCompose() {
-        benchmarkRule.benchmarkFirstCompose {
-            ListDetailPaneScaffoldTestCase().apply {
-                currentScaffoldDirective = singlePaneDirective
-            }
-        }
-    }
-
-    @Test
-    fun dualPane_firstCompose() {
-        benchmarkRule.benchmarkFirstCompose {
-            ListDetailPaneScaffoldTestCase().apply {
-                currentScaffoldDirective = dualPaneDirective
-            }
-        }
-    }
-
-    @Test
-    fun singlePane_navigateToDetail() {
-        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
-            {
-                object : ListDetailPaneScaffoldTestCase() {
-                    override fun toggleState() {
-                        currentDestination = ListDetailPaneScaffoldRole.Detail
-                    }
-                }.apply {
-                    currentScaffoldDirective = singlePaneDirective
-                }
-            },
-            assertOneRecomposition = false,
-        )
-    }
-
-    @Test
-    fun dualPane_navigateToExtra() {
-        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
-            {
-                object : ListDetailPaneScaffoldTestCase() {
-                    override fun toggleState() {
-                        currentDestination = ListDetailPaneScaffoldRole.Extra
-                    }
-                }.apply {
-                    currentScaffoldDirective = dualPaneDirective
-                }
-            },
-            assertOneRecomposition = false,
-        )
-    }
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-internal open class ListDetailPaneScaffoldTestCase : LayeredComposeTestCase(), ToggleableTestCase {
-    var currentScaffoldDirective by mutableStateOf(singlePaneDirective)
-    var currentDestination by mutableStateOf(ListDetailPaneScaffoldRole.List)
-
-    @Composable
-    override fun MeasuredContent() {
-        ListDetailPaneScaffold(
-            scaffoldState = calculateListDetailPaneScaffoldState(
-                scaffoldDirective = currentScaffoldDirective,
-                currentPaneDestination = currentDestination
-            ),
-            listPane = {
-                AnimatedPane(
-                    modifier = Modifier.testTag(tag = "ListPane")
-                ) {
-                    Box(modifier = Modifier.fillMaxSize().background(Color.Red))
-                }
-            },
-            extraPane = {
-                AnimatedPane(
-                    modifier = Modifier.testTag(tag = "ExtraPane")
-                ) {
-                    Box(modifier = Modifier.fillMaxSize().background(Color.Blue))
-                }
-            }
-        ) {
-            AnimatedPane(
-                modifier = Modifier.testTag(tag = "DetailPane")
-            ) {
-                Box(modifier = Modifier.fillMaxSize().background(Color.Yellow))
-            }
-        }
-    }
-
-    override fun toggleState() {}
-}
diff --git a/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/SupportingPaneScaffoldBenchmark.kt b/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/SupportingPaneScaffoldBenchmark.kt
deleted file mode 100644
index 932422a..0000000
--- a/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/SupportingPaneScaffoldBenchmark.kt
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.material3.adaptive.benchmark
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.adaptive.AnimatedPane
-import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
-import androidx.compose.material3.adaptive.SupportingPaneScaffold
-import androidx.compose.material3.adaptive.SupportingPaneScaffoldRole
-import androidx.compose.material3.adaptive.calculateSupportingPaneScaffoldState
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.testutils.LayeredComposeTestCase
-import androidx.compose.testutils.ToggleableTestCase
-import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
-import androidx.compose.testutils.benchmark.benchmarkFirstCompose
-import androidx.compose.testutils.benchmark.benchmarkToFirstPixel
-import androidx.compose.testutils.benchmark.toggleStateBenchmarkComposeMeasureLayout
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.testTag
-import org.junit.Rule
-import org.junit.Test
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-class SupportingPaneScaffoldBenchmark {
-    @get:Rule
-    val benchmarkRule = ComposeBenchmarkRule()
-
-    @Test
-    fun singlePane_firstPixel() {
-        benchmarkRule.benchmarkToFirstPixel {
-            SupportingPaneScaffoldTestCase().apply {
-                currentScaffoldDirective = singlePaneDirective
-            }
-        }
-    }
-
-    @Test
-    fun dualPane_firstPixel() {
-        benchmarkRule.benchmarkToFirstPixel {
-            SupportingPaneScaffoldTestCase().apply {
-                currentScaffoldDirective = dualPaneDirective
-            }
-        }
-    }
-
-    @Test
-    fun singlePane_firstCompose() {
-        benchmarkRule.benchmarkFirstCompose {
-            SupportingPaneScaffoldTestCase().apply {
-                currentScaffoldDirective = singlePaneDirective
-            }
-        }
-    }
-
-    @Test
-    fun dualPane_firstCompose() {
-        benchmarkRule.benchmarkFirstCompose {
-            SupportingPaneScaffoldTestCase().apply {
-                currentScaffoldDirective = dualPaneDirective
-            }
-        }
-    }
-
-    @Test
-    fun singlePane_navigateToSupporting() {
-        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
-            {
-                object : SupportingPaneScaffoldTestCase() {
-                    override fun toggleState() {
-                        currentDestination = SupportingPaneScaffoldRole.Supporting
-                    }
-                }.apply {
-                    currentScaffoldDirective = singlePaneDirective
-                }
-            },
-            assertOneRecomposition = false,
-        )
-    }
-
-    @Test
-    fun dualPane_navigateToExtra() {
-        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(
-            {
-                object : SupportingPaneScaffoldTestCase() {
-                    override fun toggleState() {
-                        currentDestination = SupportingPaneScaffoldRole.Extra
-                    }
-                }.apply {
-                    currentScaffoldDirective = dualPaneDirective
-                }
-            },
-            assertOneRecomposition = false,
-        )
-    }
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-internal open class SupportingPaneScaffoldTestCase : LayeredComposeTestCase(), ToggleableTestCase {
-    var currentScaffoldDirective by mutableStateOf(singlePaneDirective)
-    var currentDestination by mutableStateOf(SupportingPaneScaffoldRole.Main)
-
-    @Composable
-    override fun MeasuredContent() {
-        SupportingPaneScaffold(
-            scaffoldState = calculateSupportingPaneScaffoldState(
-                scaffoldDirective = currentScaffoldDirective,
-                currentPaneDestination = currentDestination
-            ),
-            supportingPane = {
-                AnimatedPane(
-                    modifier = Modifier.testTag(tag = "SupportingPane")
-                ) {
-                    Box(modifier = Modifier.fillMaxSize().background(Color.Red))
-                }
-            },
-            extraPane = {
-                AnimatedPane(
-                    modifier = Modifier.testTag(tag = "ExtraPane")
-                ) {
-                    Box(modifier = Modifier.fillMaxSize().background(Color.Blue))
-                }
-            }
-        ) {
-            AnimatedPane(
-                modifier = Modifier.testTag(tag = "MainPane")
-            ) {
-                Box(modifier = Modifier.fillMaxSize().background(Color.Yellow))
-            }
-        }
-    }
-
-    override fun toggleState() {}
-}
diff --git a/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt b/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt
deleted file mode 100644
index 4b2e283d..0000000
--- a/compose/material3/material3-adaptive/benchmark/src/androidTest/java/androidx/compose/material3/adaptive/benchmark/TestUtils.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.material3.adaptive.benchmark
-
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
-import androidx.compose.material3.adaptive.PaneScaffoldDirective
-import androidx.compose.ui.unit.dp
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-val singlePaneDirective = PaneScaffoldDirective(
-    contentPadding = PaddingValues(16.dp),
-    maxHorizontalPartitions = 1,
-    horizontalPartitionSpacerSize = 0.dp,
-    maxVerticalPartitions = 1,
-    verticalPartitionSpacerSize = 0.dp,
-    excludedBounds = emptyList()
-)
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-val dualPaneDirective = PaneScaffoldDirective(
-    contentPadding = PaddingValues(24.dp),
-    maxHorizontalPartitions = 2,
-    horizontalPartitionSpacerSize = 24.dp,
-    maxVerticalPartitions = 1,
-    verticalPartitionSpacerSize = 0.dp,
-    excludedBounds = emptyList()
-)
diff --git a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/ListDetailPaneScaffoldNavigatorTest.kt b/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/ListDetailPaneScaffoldNavigatorTest.kt
index 452f698..83c85bd 100644
--- a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/ListDetailPaneScaffoldNavigatorTest.kt
+++ b/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/ListDetailPaneScaffoldNavigatorTest.kt
@@ -37,7 +37,7 @@
 
     @Test
     fun singlePaneLayout_navigateTo_makeFocusPaneExpanded() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<ListDetailPaneScaffoldRole>
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator
         var canNavigateBack by Delegates.notNull<Boolean>()
 
         composeRule.setContent {
@@ -64,7 +64,7 @@
 
     @Test
     fun dualPaneLayout_navigateTo_keepFocusPaneExpanded() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<ListDetailPaneScaffoldRole>
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator
         var canNavigateBack by Delegates.notNull<Boolean>()
 
         composeRule.setContent {
@@ -91,7 +91,7 @@
 
     @Test
     fun singlePaneLayout_navigateBack_makeFocusPaneHidden() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<ListDetailPaneScaffoldRole>
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator
         var canNavigateBack by Delegates.notNull<Boolean>()
 
         composeRule.setContent {
@@ -123,7 +123,7 @@
 
     @Test
     fun dualPaneLayout_enforceScaffoldValueChange_cannotNavigateBack() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<ListDetailPaneScaffoldRole>
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator
 
         composeRule.setContent {
             scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
@@ -142,7 +142,7 @@
 
     @Test
     fun dualPaneLayout_notEnforceScaffoldValueChange_canNavigateBack() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<ListDetailPaneScaffoldRole>
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator
 
         composeRule.setContent {
             scaffoldNavigator = rememberListDetailPaneScaffoldNavigator(
@@ -171,7 +171,7 @@
 
     @Test
     fun singlePaneToDualPaneLayout_enforceScaffoldValueChange_cannotNavigateBack() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<ListDetailPaneScaffoldRole>
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator
         val mockCurrentScaffoldDirective = mutableStateOf(MockSinglePaneScaffoldDirective)
 
         composeRule.setContent {
diff --git a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/SupportingPaneScaffoldNavigatorTest.kt b/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/SupportingPaneScaffoldNavigatorTest.kt
index a3ad7b67..34d2315 100644
--- a/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/SupportingPaneScaffoldNavigatorTest.kt
+++ b/compose/material3/material3-adaptive/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/SupportingPaneScaffoldNavigatorTest.kt
@@ -37,7 +37,7 @@
 
     @Test
     fun singlePaneLayout_navigateTo_makeFocusPaneExpanded() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<SupportingPaneScaffoldRole>
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator
         var canNavigateBack by Delegates.notNull<Boolean>()
 
         composeRule.setContent {
@@ -64,7 +64,7 @@
 
     @Test
     fun dualPaneLayout_navigateTo_keepFocusPaneExpanded() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<SupportingPaneScaffoldRole>
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator
         var canNavigateBack by Delegates.notNull<Boolean>()
 
         composeRule.setContent {
@@ -91,7 +91,7 @@
 
     @Test
     fun singlePaneLayout_navigateBack_makeFocusPaneHidden() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<SupportingPaneScaffoldRole>
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator
         var canNavigateBack by Delegates.notNull<Boolean>()
 
         composeRule.setContent {
@@ -123,7 +123,7 @@
 
     @Test
     fun dualPaneLayout_enforceScaffoldChange_cannotNavigateBack() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<SupportingPaneScaffoldRole>
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator
 
         composeRule.setContent {
             scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
@@ -142,7 +142,7 @@
 
     @Test
     fun dualPaneLayout_notEnforceScaffoldValueChange_canNavigateBack() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<SupportingPaneScaffoldRole>
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator
 
         composeRule.setContent {
             scaffoldNavigator = rememberSupportingPaneScaffoldNavigator(
@@ -171,7 +171,7 @@
 
     @Test
     fun singlePaneToDualPaneLayout_enforceScaffoldValueChange_cannotNavigateBack() {
-        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator<SupportingPaneScaffoldRole>
+        lateinit var scaffoldNavigator: ThreePaneScaffoldNavigator
         val mockCurrentScaffoldDirective = mutableStateOf(MockSinglePaneScaffoldDirective)
 
         composeRule.setContent {
diff --git a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/ListDetailPaneScaffold.android.kt b/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/ListDetailPaneScaffold.android.kt
index 68f178a..ef08697 100644
--- a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/ListDetailPaneScaffold.android.kt
+++ b/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/ListDetailPaneScaffold.android.kt
@@ -71,13 +71,13 @@
         calculateStandardPaneScaffoldDirective(currentWindowAdaptiveInfo()),
     adaptStrategies: ThreePaneScaffoldAdaptStrategies =
         ListDetailPaneScaffoldDefaults.adaptStrategies(),
-    currentPaneDestination: ListDetailPaneScaffoldRole = ListDetailPaneScaffoldRole.List
+    currentPaneDestination: ThreePaneScaffoldRole = ListDetailPaneScaffoldRole.List
 ): ThreePaneScaffoldState = ThreePaneScaffoldStateImpl(
     scaffoldDirective,
     calculateThreePaneScaffoldValue(
         scaffoldDirective.maxHorizontalPartitions,
         adaptStrategies,
-        currentPaneDestination.threePaneScaffoldRole
+        currentPaneDestination
     )
 )
 
@@ -106,22 +106,28 @@
 }
 
 /**
- * The set of the available pane roles of [ListDetailPaneScaffold].
+ * The set of the available pane roles of [ListDetailPaneScaffold]. Basically those values are
+ * aliases of [ThreePaneScaffoldRole]. We suggest you to use the values defined here instead of
+ * the raw [ThreePaneScaffoldRole] under the context of [ListDetailPaneScaffold] for better
+ * code clarity.
  */
 @ExperimentalMaterial3AdaptiveApi
-enum class ListDetailPaneScaffoldRole(internal val threePaneScaffoldRole: ThreePaneScaffoldRole) {
+object ListDetailPaneScaffoldRole {
     /**
-     * The list pane of [ListDetailPaneScaffold]. It is mapped to [ThreePaneScaffoldRole.Secondary].
+     * The list pane of [ListDetailPaneScaffold]. It is an alias of
+     * [ThreePaneScaffoldRole.Secondary].
      */
-    List(ThreePaneScaffoldRole.Secondary),
+    val List = ThreePaneScaffoldRole.Secondary
 
     /**
-     * The detail pane of [ListDetailPaneScaffold]. It is mapped to [ThreePaneScaffoldRole.Primary].
+     * The detail pane of [ListDetailPaneScaffold]. It is an alias of
+     * [ThreePaneScaffoldRole.Primary].
      */
-    Detail(ThreePaneScaffoldRole.Primary),
+    val Detail = ThreePaneScaffoldRole.Primary
 
     /**
-     * The extra pane of [ListDetailPaneScaffold]. It is mapped to [ThreePaneScaffoldRole.Tertiary].
+     * The extra pane of [ListDetailPaneScaffold]. It is an alias of
+     * [ThreePaneScaffoldRole.Tertiary].
      */
-    Extra(ThreePaneScaffoldRole.Tertiary);
+    val Extra = ThreePaneScaffoldRole.Tertiary
 }
diff --git a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/SupportingPaneScaffold.android.kt b/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/SupportingPaneScaffold.android.kt
index 42535b8..b6020ce 100644
--- a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/SupportingPaneScaffold.android.kt
+++ b/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/SupportingPaneScaffold.android.kt
@@ -56,13 +56,13 @@
         calculateStandardPaneScaffoldDirective(currentWindowAdaptiveInfo()),
     adaptStrategies: ThreePaneScaffoldAdaptStrategies =
         SupportingPaneScaffoldDefaults.adaptStrategies(),
-    currentPaneDestination: SupportingPaneScaffoldRole = SupportingPaneScaffoldRole.Main
+    currentPaneDestination: ThreePaneScaffoldRole = SupportingPaneScaffoldRole.Main
 ): ThreePaneScaffoldState = ThreePaneScaffoldStateImpl(
     scaffoldDirective,
     calculateThreePaneScaffoldValue(
         scaffoldDirective.maxHorizontalPartitions,
         adaptStrategies,
-        currentPaneDestination.threePaneScaffoldRole
+        currentPaneDestination
     )
 )
 
@@ -91,21 +91,28 @@
 }
 
 /**
- * The set of the available pane roles of [SupportingPaneScaffold].
+ * The set of the available pane roles of [SupportingPaneScaffold]. Basically those values are
+ * aliases of [ThreePaneScaffoldRole]. We suggest you to use the values defined here instead of
+ * the raw [ThreePaneScaffoldRole] under the context of [SupportingPaneScaffold] for better
+ * code clarity.
  */
 @ExperimentalMaterial3AdaptiveApi
-enum class SupportingPaneScaffoldRole(internal val threePaneScaffoldRole: ThreePaneScaffoldRole) {
+object SupportingPaneScaffoldRole {
     /**
-     * The main pane of [SupportingPaneScaffold]. It is mapped to [ThreePaneScaffoldRole.Primary].
+     * The main pane of [SupportingPaneScaffold]. It is an alias of
+     * [ThreePaneScaffoldRole.Primary].
      */
-    Main(ThreePaneScaffoldRole.Primary),
+    val Main = ThreePaneScaffoldRole.Primary
+
     /**
-     * The supporting pane of [SupportingPaneScaffold]. It is mapped to
+     * The supporting pane of [SupportingPaneScaffold]. It is an alias of
      * [ThreePaneScaffoldRole.Secondary].
      */
-    Supporting(ThreePaneScaffoldRole.Secondary),
+    val Supporting = ThreePaneScaffoldRole.Secondary
+
     /**
-     * The extra pane of [SupportingPaneScaffold]. It is mapped to [ThreePaneScaffoldRole.Tertiary].
+     * The extra pane of [SupportingPaneScaffold]. It is an alias of
+     * [ThreePaneScaffoldRole.Tertiary].
      */
-    Extra(ThreePaneScaffoldRole.Tertiary)
+    val Extra = ThreePaneScaffoldRole.Tertiary
 }
diff --git a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldNavigator.android.kt b/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldNavigator.android.kt
index 0e269d3..5759fb7 100644
--- a/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldNavigator.android.kt
+++ b/compose/material3/material3-adaptive/src/androidMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldNavigator.android.kt
@@ -22,12 +22,10 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateListOf
 import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.Saver
 import androidx.compose.runtime.saveable.listSaver
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.util.fastMap
 
 /**
  * The navigation integration entry point of [ThreePaneScaffold] implementations.
@@ -40,7 +38,7 @@
  */
 @ExperimentalMaterial3AdaptiveApi
 @Stable
-interface ThreePaneScaffoldNavigator<T> {
+interface ThreePaneScaffoldNavigator {
     /**
      * The current scaffold state provided by the navigator. It's supposed to be auto-updated
      * whenever a navigation operation is performed.
@@ -50,7 +48,7 @@
     /**
      * Navigates to a new pane destination.
      */
-    fun navigateTo(pane: T)
+    fun navigateTo(pane: ThreePaneScaffoldRole)
 
     /**
      * Returns `true` if there is a previous destination to navigate back to.
@@ -89,18 +87,13 @@
         calculateStandardPaneScaffoldDirective(currentWindowAdaptiveInfo()),
     adaptStrategies: ThreePaneScaffoldAdaptStrategies =
         ListDetailPaneScaffoldDefaults.adaptStrategies(),
-    initialDestinationHistory: List<ListDetailPaneScaffoldRole> =
-        listOf(ListDetailPaneScaffoldRole.List)
-): ThreePaneScaffoldNavigator<ListDetailPaneScaffoldRole> {
-    val internalNavigator = rememberDefaultThreePaneScaffoldNavigator(
+    initialDestinationHistory: List<ThreePaneScaffoldRole> = listOf(ListDetailPaneScaffoldRole.List)
+): ThreePaneScaffoldNavigator =
+    rememberThreePaneScaffoldNavigator(
         scaffoldDirective,
         adaptStrategies,
-        initialDestinationHistory.fastMap { it.threePaneScaffoldRole }
+        initialDestinationHistory
     )
-    return remember(internalNavigator) {
-        DefaultListDetailPaneScaffoldNavigator(internalNavigator)
-    }
-}
 
 /**
  * Returns a remembered default implementation of [ThreePaneScaffoldNavigator] for
@@ -122,48 +115,66 @@
         calculateStandardPaneScaffoldDirective(currentWindowAdaptiveInfo()),
     adaptStrategies: ThreePaneScaffoldAdaptStrategies =
         SupportingPaneScaffoldDefaults.adaptStrategies(),
-    initialDestinationHistory: List<SupportingPaneScaffoldRole> =
+    initialDestinationHistory: List<ThreePaneScaffoldRole> =
         listOf(SupportingPaneScaffoldRole.Main)
-): ThreePaneScaffoldNavigator<SupportingPaneScaffoldRole> {
-    val internalNavigator = rememberDefaultThreePaneScaffoldNavigator(
+): ThreePaneScaffoldNavigator =
+    rememberThreePaneScaffoldNavigator(
         scaffoldDirective,
         adaptStrategies,
-        initialDestinationHistory.fastMap { it.threePaneScaffoldRole }
+        initialDestinationHistory
     )
-    return remember(internalNavigator) {
-        DefaultSupportingPaneScaffoldNavigator(internalNavigator)
+
+@ExperimentalMaterial3AdaptiveApi
+@Composable
+internal fun rememberThreePaneScaffoldNavigator(
+    scaffoldDirective: PaneScaffoldDirective,
+    adaptStrategies: ThreePaneScaffoldAdaptStrategies,
+    initialDestinationHistory: List<ThreePaneScaffoldRole>
+): ThreePaneScaffoldNavigator =
+    rememberSaveable(
+        saver = DefaultThreePaneScaffoldNavigator.saver(scaffoldDirective, adaptStrategies)
+    ) {
+        DefaultThreePaneScaffoldNavigator(
+            initialDestinationHistory = initialDestinationHistory,
+            initialScaffoldDirective = scaffoldDirective,
+            initialAdaptStrategies = adaptStrategies
+        )
+    }.apply {
+        this.scaffoldDirective = scaffoldDirective
+        this.adaptStrategies = adaptStrategies
     }
-}
 
 @OptIn(ExperimentalMaterial3AdaptiveApi::class)
 internal class DefaultThreePaneScaffoldNavigator(
     initialDestinationHistory: List<ThreePaneScaffoldRole>,
     initialScaffoldDirective: PaneScaffoldDirective,
     initialAdaptStrategies: ThreePaneScaffoldAdaptStrategies,
-) : ThreePaneScaffoldState {
+) : ThreePaneScaffoldNavigator, ThreePaneScaffoldState {
 
     private val destinationHistory = mutableStateListOf<ThreePaneScaffoldRole>().apply {
         addAll(initialDestinationHistory)
     }
 
+    override val scaffoldState = this
+
     override var scaffoldDirective by mutableStateOf(initialScaffoldDirective)
+
     var adaptStrategies by mutableStateOf(initialAdaptStrategies)
 
-    val currentDestination: ThreePaneScaffoldRole?
-        get() = destinationHistory.lastOrNull()
+    val currentDestination: ThreePaneScaffoldRole? get() = destinationHistory.lastOrNull()
 
     override val scaffoldValue by derivedStateOf {
         calculateScaffoldValue(currentDestination)
     }
 
-    fun navigateTo(pane: ThreePaneScaffoldRole) {
+    override fun navigateTo(pane: ThreePaneScaffoldRole) {
         destinationHistory.add(pane)
     }
 
-    fun canNavigateBack(scaffoldValueMustChange: Boolean): Boolean =
+    override fun canNavigateBack(scaffoldValueMustChange: Boolean): Boolean =
         getPreviousDestinationIndex(scaffoldValueMustChange) >= 0
 
-    fun navigateBack(popUntilScaffoldValueChange: Boolean): Boolean {
+    override fun navigateBack(popUntilScaffoldValueChange: Boolean): Boolean {
         val previousDestinationIndex = getPreviousDestinationIndex(popUntilScaffoldValueChange)
         if (previousDestinationIndex < 0) {
             destinationHistory.clear()
@@ -211,7 +222,7 @@
             initialAdaptStrategies: ThreePaneScaffoldAdaptStrategies
         ): Saver<DefaultThreePaneScaffoldNavigator, *> = listSaver(
             save = {
-                it.destinationHistory.toList()
+                it.destinationHistory
             },
             restore = {
                 DefaultThreePaneScaffoldNavigator(
@@ -223,60 +234,3 @@
         )
     }
 }
-
-@ExperimentalMaterial3AdaptiveApi
-@Composable
-internal fun rememberDefaultThreePaneScaffoldNavigator(
-    scaffoldDirective: PaneScaffoldDirective,
-    adaptStrategies: ThreePaneScaffoldAdaptStrategies,
-    initialDestinationHistory: List<ThreePaneScaffoldRole>
-): DefaultThreePaneScaffoldNavigator =
-    rememberSaveable(
-        saver = DefaultThreePaneScaffoldNavigator.saver(
-            scaffoldDirective,
-            adaptStrategies,
-        )
-    ) {
-        DefaultThreePaneScaffoldNavigator(
-            initialDestinationHistory = initialDestinationHistory,
-            initialScaffoldDirective = scaffoldDirective,
-            initialAdaptStrategies = adaptStrategies
-        )
-    }.apply {
-        this.scaffoldDirective = scaffoldDirective
-        this.adaptStrategies = adaptStrategies
-    }
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private class DefaultListDetailPaneScaffoldNavigator(
-    val internalNavigator: DefaultThreePaneScaffoldNavigator
-) : ThreePaneScaffoldNavigator<ListDetailPaneScaffoldRole> {
-    override val scaffoldState get() = internalNavigator
-
-    override fun navigateTo(pane: ListDetailPaneScaffoldRole) {
-        internalNavigator.navigateTo(pane.threePaneScaffoldRole)
-    }
-
-    override fun canNavigateBack(scaffoldValueMustChange: Boolean): Boolean =
-        internalNavigator.canNavigateBack(scaffoldValueMustChange)
-
-    override fun navigateBack(popUntilScaffoldValueChange: Boolean): Boolean =
-        internalNavigator.navigateBack(popUntilScaffoldValueChange)
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private class DefaultSupportingPaneScaffoldNavigator(
-    val internalNavigator: DefaultThreePaneScaffoldNavigator
-) : ThreePaneScaffoldNavigator<SupportingPaneScaffoldRole> {
-    override val scaffoldState get() = internalNavigator
-
-    override fun navigateTo(pane: SupportingPaneScaffoldRole) {
-        internalNavigator.navigateTo(pane.threePaneScaffoldRole)
-    }
-
-    override fun canNavigateBack(scaffoldValueMustChange: Boolean): Boolean =
-        internalNavigator.canNavigateBack(scaffoldValueMustChange)
-
-    override fun navigateBack(popUntilScaffoldValueChange: Boolean): Boolean =
-        internalNavigator.navigateBack(popUntilScaffoldValueChange)
-}
diff --git a/compose/material3/material3-adaptive/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldValueTest.kt b/compose/material3/material3-adaptive/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldValueTest.kt
index 9ece26c..32cfb93 100644
--- a/compose/material3/material3-adaptive/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldValueTest.kt
+++ b/compose/material3/material3-adaptive/src/androidUnitTest/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldValueTest.kt
@@ -31,7 +31,10 @@
             adaptStrategies = MockAdaptStrategies
         )
         scaffoldState.assertState(ThreePaneScaffoldRole.Primary, PaneAdaptedValue.Expanded)
-        scaffoldState.assertState(ThreePaneScaffoldRole.Secondary, SecondaryPaneAdaptedState)
+        scaffoldState.assertState(
+            ThreePaneScaffoldRole.Secondary,
+            SecondaryPaneAdaptedState
+        )
         scaffoldState.assertState(ThreePaneScaffoldRole.Tertiary, TertiaryPaneAdaptedState)
     }
 
@@ -40,10 +43,12 @@
         val scaffoldState = calculateThreePaneScaffoldValue(
             maxHorizontalPartitions = 1,
             adaptStrategies = MockAdaptStrategies,
-            currentFocus = ThreePaneScaffoldRole.Secondary
+            currentDestination = ThreePaneScaffoldRole.Secondary
         )
         scaffoldState.assertState(ThreePaneScaffoldRole.Primary, PrimaryPaneAdaptedState)
-        scaffoldState.assertState(ThreePaneScaffoldRole.Secondary, PaneAdaptedValue.Expanded)
+        scaffoldState.assertState(
+            ThreePaneScaffoldRole.Secondary,
+            PaneAdaptedValue.Expanded)
         scaffoldState.assertState(ThreePaneScaffoldRole.Tertiary, TertiaryPaneAdaptedState)
     }
 
@@ -54,7 +59,9 @@
             adaptStrategies = MockAdaptStrategies
         )
         scaffoldState.assertState(ThreePaneScaffoldRole.Primary, PaneAdaptedValue.Expanded)
-        scaffoldState.assertState(ThreePaneScaffoldRole.Secondary, PaneAdaptedValue.Expanded)
+        scaffoldState.assertState(
+            ThreePaneScaffoldRole.Secondary, PaneAdaptedValue.Expanded
+        )
         scaffoldState.assertState(ThreePaneScaffoldRole.Tertiary, TertiaryPaneAdaptedState)
     }
 
@@ -63,10 +70,12 @@
         val scaffoldState = calculateThreePaneScaffoldValue(
             maxHorizontalPartitions = 2,
             adaptStrategies = MockAdaptStrategies,
-            currentFocus = ThreePaneScaffoldRole.Tertiary
+            currentDestination = ThreePaneScaffoldRole.Tertiary
         )
         scaffoldState.assertState(ThreePaneScaffoldRole.Primary, PaneAdaptedValue.Expanded)
-        scaffoldState.assertState(ThreePaneScaffoldRole.Secondary, SecondaryPaneAdaptedState)
+        scaffoldState.assertState(
+            ThreePaneScaffoldRole.Secondary, SecondaryPaneAdaptedState
+        )
         scaffoldState.assertState(ThreePaneScaffoldRole.Tertiary, PaneAdaptedValue.Expanded)
     }
 
diff --git a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffold.kt b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffold.kt
index 4e0abed..134cfcf 100644
--- a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffold.kt
+++ b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffold.kt
@@ -185,8 +185,8 @@
 ) {
 
     /**
-     * Resolves and returns the [EnterTransition] for the given [ThreePaneScaffoldRole] at the given
-     * [ThreePaneScaffoldHorizontalOrder].
+     * Resolves and returns the [EnterTransition] for the given [ThreePaneScaffoldRole]
+     * at the given [ThreePaneScaffoldHorizontalOrder].
      */
     fun enterTransition(
         role: ThreePaneScaffoldRole,
@@ -203,8 +203,8 @@
     }
 
     /**
-     * Resolves and returns the [ExitTransition] for the given [ThreePaneScaffoldRole] at the given
-     * [ThreePaneScaffoldHorizontalOrder].
+     * Resolves and returns the [ExitTransition] for the given [ThreePaneScaffoldRole]
+     * at the given [ThreePaneScaffoldHorizontalOrder].
      */
     fun exitTransition(
         role: ThreePaneScaffoldRole,
diff --git a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldValue.kt b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldValue.kt
index f1e1931..30f18cb 100644
--- a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldValue.kt
+++ b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/ThreePaneScaffoldValue.kt
@@ -31,33 +31,33 @@
 
 /**
  * Calculates the current adapted value of [ThreePaneScaffold] according to the given
- * [maxHorizontalPartitions], [adaptStrategies] and [currentFocus]. The returned value can be used
- * as a unique representation of the current layout structure.
+ * [maxHorizontalPartitions], [adaptStrategies] and [currentDestination]. The returned value can be
+ * used as a unique representation of the current layout structure.
  *
- * The function will treat the current focus as the highest priority and then adapt the rest
+ * The function will treat the current destination as the highest priority and then adapt the rest
  * panes according to the order of [ThreePaneScaffoldRole.Primary],
- * [ThreePaneScaffoldRole.Secondary] and [ThreePaneScaffoldRole.Tertiary]. If there are still
- * remaining partitions to put the pane, the pane will be set as [PaneAdaptedValue.Expanded],
- * otherwise it will be adapted according to its associated [AdaptStrategy].
+ * [ThreePaneScaffoldRole.Secondary] and [ThreePaneScaffoldRole.Tertiary]. If there
+ * are still remaining partitions to put the pane, the pane will be set as
+ * [PaneAdaptedValue.Expanded], otherwise it will be adapted according to its associated
+ * [AdaptStrategy].
  *
  * @param maxHorizontalPartitions The maximum allowed partitions along the horizontal axis, i.e.
- *                                how many expanded panes can be shown at the same time.
+ *        how many expanded panes can be shown at the same time.
  * @param adaptStrategies The adapt strategies of each pane role that [ThreePaneScaffold] supports,
- *                        the default value will be
- *                        [ThreePaneScaffoldDefaults.threePaneScaffoldAdaptStrategies].
- * @param currentFocus The current focused pane, which will be treated as the highest priority, can
- *                     be `null`.
+ *        the default value will be [ThreePaneScaffoldDefaults.threePaneScaffoldAdaptStrategies].
+ * @param currentDestination The current pane destination, which will be treated as having
+ *        the highest priority, can be `null`.
  */
 @ExperimentalMaterial3AdaptiveApi
 fun calculateThreePaneScaffoldValue(
     maxHorizontalPartitions: Int,
     adaptStrategies: ThreePaneScaffoldAdaptStrategies = ThreePaneScaffoldDefaults.adaptStrategies(),
-    currentFocus: ThreePaneScaffoldRole? = null,
+    currentDestination: ThreePaneScaffoldRole? = null,
 ): ThreePaneScaffoldValue {
-    var expandedCount = if (currentFocus != null) 1 else 0
+    var expandedCount = if (currentDestination != null) 1 else 0
     return buildThreePaneScaffoldValue { role ->
         when {
-            role == currentFocus -> PaneAdaptedValue.Expanded
+            role == currentDestination -> PaneAdaptedValue.Expanded
             expandedCount < maxHorizontalPartitions -> {
                 expandedCount++
                 PaneAdaptedValue.Expanded
diff --git a/compose/material3/material3/api/1.2.0-beta01.txt b/compose/material3/material3/api/1.2.0-beta01.txt
index 7138834..4a436c2 100644
--- a/compose/material3/material3/api/1.2.0-beta01.txt
+++ b/compose/material3/material3/api/1.2.0-beta01.txt
@@ -1617,7 +1617,7 @@
     method @androidx.compose.runtime.Composable public static void Switch(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit>? onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? thumbContent, optional boolean enabled, optional androidx.compose.material3.SwitchColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
   }
 
-  public interface TabIndicatorScope {
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public interface TabIndicatorScope {
     method public androidx.compose.ui.Modifier tabIndicatorLayout(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function4<? super androidx.compose.ui.layout.MeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? super java.util.List<androidx.compose.material3.TabPosition>,? extends androidx.compose.ui.layout.MeasureResult> measure);
     method public androidx.compose.ui.Modifier tabIndicatorOffset(androidx.compose.ui.Modifier, int selectedTabIndex, optional boolean matchContentSize);
   }
diff --git a/compose/material3/material3/api/current.ignore b/compose/material3/material3/api/current.ignore
index 78a559f..5240c40 100644
--- a/compose/material3/material3/api/current.ignore
+++ b/compose/material3/material3/api/current.ignore
@@ -1,45 +1,17 @@
 // Baseline format: 1.0
-BecameUnchecked: Field TimePickerState.hour:
-    Removed Field TimePickerState.hour from compatibility checked API surface
-BecameUnchecked: Field TimePickerState.is24hour:
-    Removed Field TimePickerState.is24hour from compatibility checked API surface
-BecameUnchecked: Field TimePickerState.minute:
-    Removed Field TimePickerState.minute from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState:
-    Removed class androidx.compose.material3.TimePickerState from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState#Companion:
-    Removed field androidx.compose.material3.TimePickerState.Companion from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState#TimePickerState(int, int, boolean):
-    Removed constructor androidx.compose.material3.TimePickerState(int,int,boolean) from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState#TimePickerState(int, int, boolean) parameter #0:
-    Removed parameter initialHour in androidx.compose.material3.TimePickerState(int initialHour, int initialMinute, boolean is24Hour) from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState#TimePickerState(int, int, boolean) parameter #1:
-    Removed parameter initialMinute in androidx.compose.material3.TimePickerState(int initialHour, int initialMinute, boolean is24Hour) from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState#TimePickerState(int, int, boolean) parameter #2:
-    Removed parameter is24Hour in androidx.compose.material3.TimePickerState(int initialHour, int initialMinute, boolean is24Hour) from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState#getHour():
-    Removed method androidx.compose.material3.TimePickerState.getHour() from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState#getMinute():
-    Removed method androidx.compose.material3.TimePickerState.getMinute() from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState#is24hour():
-    Removed method androidx.compose.material3.TimePickerState.is24hour() from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState#settle(kotlin.coroutines.Continuation<? super kotlin.Unit>):
-    Removed method androidx.compose.material3.TimePickerState.settle(kotlin.coroutines.Continuation<? super kotlin.Unit>) from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState#settle(kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #0:
-    Removed parameter arg1 in androidx.compose.material3.TimePickerState.settle(kotlin.coroutines.Continuation<? super kotlin.Unit> arg1) from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState.Companion:
-    Removed class androidx.compose.material3.TimePickerState.Companion from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState.Companion#Saver():
-    Removed method androidx.compose.material3.TimePickerState.Companion.Saver() from compatibility checked API surface
-
-
-DefaultValueChange: androidx.compose.material3.SnackbarHostState#showSnackbar(String, String, boolean, androidx.compose.material3.SnackbarDuration, kotlin.coroutines.Continuation<? super androidx.compose.material3.SnackbarResult>) parameter #4:
-    Attempted to remove default value from parameter arg5 in androidx.compose.material3.SnackbarHostState.showSnackbar
-
-
-RemovedClass: androidx.compose.material3.ExposedDropdownMenuKt:
-    Removed class androidx.compose.material3.ExposedDropdownMenuKt
-RemovedClass: androidx.compose.material3.SearchBarKt:
-    Removed class androidx.compose.material3.SearchBarKt
-RemovedClass: androidx.compose.material3.SwipeToDismissKt:
-    Removed class androidx.compose.material3.SwipeToDismissKt
+BecameUnchecked: androidx.compose.material3.TabIndicatorScope:
+    Removed class androidx.compose.material3.TabIndicatorScope from compatibility checked API surface
+BecameUnchecked: androidx.compose.material3.TabIndicatorScope#tabIndicatorLayout(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function4<? super androidx.compose.ui.layout.MeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? super java.util.List<androidx.compose.material3.TabPosition>,? extends androidx.compose.ui.layout.MeasureResult>):
+    Removed method androidx.compose.material3.TabIndicatorScope.tabIndicatorLayout(androidx.compose.ui.Modifier,kotlin.jvm.functions.Function4<? super androidx.compose.ui.layout.MeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? super java.util.List<androidx.compose.material3.TabPosition>,? extends androidx.compose.ui.layout.MeasureResult>) from compatibility checked API surface
+BecameUnchecked: androidx.compose.material3.TabIndicatorScope#tabIndicatorLayout(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function4<? super androidx.compose.ui.layout.MeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? super java.util.List<androidx.compose.material3.TabPosition>,? extends androidx.compose.ui.layout.MeasureResult>) parameter #0:
+    Removed parameter arg1 in androidx.compose.material3.TabIndicatorScope.tabIndicatorLayout(androidx.compose.ui.Modifier arg1, kotlin.jvm.functions.Function4<? super androidx.compose.ui.layout.MeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? super java.util.List<androidx.compose.material3.TabPosition>,? extends androidx.compose.ui.layout.MeasureResult> measure) from compatibility checked API surface
+BecameUnchecked: androidx.compose.material3.TabIndicatorScope#tabIndicatorLayout(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function4<? super androidx.compose.ui.layout.MeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? super java.util.List<androidx.compose.material3.TabPosition>,? extends androidx.compose.ui.layout.MeasureResult>) parameter #1:
+    Removed parameter measure in androidx.compose.material3.TabIndicatorScope.tabIndicatorLayout(androidx.compose.ui.Modifier arg1, kotlin.jvm.functions.Function4<? super androidx.compose.ui.layout.MeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? super java.util.List<androidx.compose.material3.TabPosition>,? extends androidx.compose.ui.layout.MeasureResult> measure) from compatibility checked API surface
+BecameUnchecked: androidx.compose.material3.TabIndicatorScope#tabIndicatorOffset(androidx.compose.ui.Modifier, int, boolean):
+    Removed method androidx.compose.material3.TabIndicatorScope.tabIndicatorOffset(androidx.compose.ui.Modifier,int,boolean) from compatibility checked API surface
+BecameUnchecked: androidx.compose.material3.TabIndicatorScope#tabIndicatorOffset(androidx.compose.ui.Modifier, int, boolean) parameter #0:
+    Removed parameter arg1 in androidx.compose.material3.TabIndicatorScope.tabIndicatorOffset(androidx.compose.ui.Modifier arg1, int selectedTabIndex, boolean matchContentSize) from compatibility checked API surface
+BecameUnchecked: androidx.compose.material3.TabIndicatorScope#tabIndicatorOffset(androidx.compose.ui.Modifier, int, boolean) parameter #1:
+    Removed parameter selectedTabIndex in androidx.compose.material3.TabIndicatorScope.tabIndicatorOffset(androidx.compose.ui.Modifier arg1, int selectedTabIndex, boolean matchContentSize) from compatibility checked API surface
+BecameUnchecked: androidx.compose.material3.TabIndicatorScope#tabIndicatorOffset(androidx.compose.ui.Modifier, int, boolean) parameter #2:
+    Removed parameter matchContentSize in androidx.compose.material3.TabIndicatorScope.tabIndicatorOffset(androidx.compose.ui.Modifier arg1, int selectedTabIndex, boolean matchContentSize) from compatibility checked API surface
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 7138834..4a436c2 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -1617,7 +1617,7 @@
     method @androidx.compose.runtime.Composable public static void Switch(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit>? onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? thumbContent, optional boolean enabled, optional androidx.compose.material3.SwitchColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
   }
 
-  public interface TabIndicatorScope {
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public interface TabIndicatorScope {
     method public androidx.compose.ui.Modifier tabIndicatorLayout(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function4<? super androidx.compose.ui.layout.MeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? super java.util.List<androidx.compose.material3.TabPosition>,? extends androidx.compose.ui.layout.MeasureResult> measure);
     method public androidx.compose.ui.Modifier tabIndicatorOffset(androidx.compose.ui.Modifier, int selectedTabIndex, optional boolean matchContentSize);
   }
diff --git a/compose/material3/material3/api/restricted_1.2.0-beta01.txt b/compose/material3/material3/api/restricted_1.2.0-beta01.txt
index 7138834..4a436c2 100644
--- a/compose/material3/material3/api/restricted_1.2.0-beta01.txt
+++ b/compose/material3/material3/api/restricted_1.2.0-beta01.txt
@@ -1617,7 +1617,7 @@
     method @androidx.compose.runtime.Composable public static void Switch(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit>? onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? thumbContent, optional boolean enabled, optional androidx.compose.material3.SwitchColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
   }
 
-  public interface TabIndicatorScope {
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public interface TabIndicatorScope {
     method public androidx.compose.ui.Modifier tabIndicatorLayout(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function4<? super androidx.compose.ui.layout.MeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? super java.util.List<androidx.compose.material3.TabPosition>,? extends androidx.compose.ui.layout.MeasureResult> measure);
     method public androidx.compose.ui.Modifier tabIndicatorOffset(androidx.compose.ui.Modifier, int selectedTabIndex, optional boolean matchContentSize);
   }
diff --git a/compose/material3/material3/api/restricted_current.ignore b/compose/material3/material3/api/restricted_current.ignore
index 65af023..5240c40 100644
--- a/compose/material3/material3/api/restricted_current.ignore
+++ b/compose/material3/material3/api/restricted_current.ignore
@@ -1,37 +1,17 @@
 // Baseline format: 1.0
-BecameUnchecked: Field TimePickerState.hour:
-    Removed Field TimePickerState.hour from compatibility checked API surface
-BecameUnchecked: Field TimePickerState.is24hour:
-    Removed Field TimePickerState.is24hour from compatibility checked API surface
-BecameUnchecked: Field TimePickerState.minute:
-    Removed Field TimePickerState.minute from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState:
-    Removed class androidx.compose.material3.TimePickerState from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState#Companion:
-    Removed field androidx.compose.material3.TimePickerState.Companion from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState#TimePickerState(int, int, boolean):
-    Removed constructor androidx.compose.material3.TimePickerState(int,int,boolean) from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState#TimePickerState(int, int, boolean) parameter #0:
-    Removed parameter initialHour in androidx.compose.material3.TimePickerState(int initialHour, int initialMinute, boolean is24Hour) from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState#TimePickerState(int, int, boolean) parameter #1:
-    Removed parameter initialMinute in androidx.compose.material3.TimePickerState(int initialHour, int initialMinute, boolean is24Hour) from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState#TimePickerState(int, int, boolean) parameter #2:
-    Removed parameter is24Hour in androidx.compose.material3.TimePickerState(int initialHour, int initialMinute, boolean is24Hour) from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState#getHour():
-    Removed method androidx.compose.material3.TimePickerState.getHour() from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState#getMinute():
-    Removed method androidx.compose.material3.TimePickerState.getMinute() from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState#is24hour():
-    Removed method androidx.compose.material3.TimePickerState.is24hour() from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState#settle(kotlin.coroutines.Continuation<? super kotlin.Unit>):
-    Removed method androidx.compose.material3.TimePickerState.settle(kotlin.coroutines.Continuation<? super kotlin.Unit>) from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState#settle(kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #0:
-    Removed parameter arg1 in androidx.compose.material3.TimePickerState.settle(kotlin.coroutines.Continuation<? super kotlin.Unit> arg1) from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState.Companion:
-    Removed class androidx.compose.material3.TimePickerState.Companion from compatibility checked API surface
-BecameUnchecked: androidx.compose.material3.TimePickerState.Companion#Saver():
-    Removed method androidx.compose.material3.TimePickerState.Companion.Saver() from compatibility checked API surface
-
-
-DefaultValueChange: androidx.compose.material3.SnackbarHostState#showSnackbar(String, String, boolean, androidx.compose.material3.SnackbarDuration, kotlin.coroutines.Continuation<? super androidx.compose.material3.SnackbarResult>) parameter #4:
-    Attempted to remove default value from parameter arg5 in androidx.compose.material3.SnackbarHostState.showSnackbar
+BecameUnchecked: androidx.compose.material3.TabIndicatorScope:
+    Removed class androidx.compose.material3.TabIndicatorScope from compatibility checked API surface
+BecameUnchecked: androidx.compose.material3.TabIndicatorScope#tabIndicatorLayout(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function4<? super androidx.compose.ui.layout.MeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? super java.util.List<androidx.compose.material3.TabPosition>,? extends androidx.compose.ui.layout.MeasureResult>):
+    Removed method androidx.compose.material3.TabIndicatorScope.tabIndicatorLayout(androidx.compose.ui.Modifier,kotlin.jvm.functions.Function4<? super androidx.compose.ui.layout.MeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? super java.util.List<androidx.compose.material3.TabPosition>,? extends androidx.compose.ui.layout.MeasureResult>) from compatibility checked API surface
+BecameUnchecked: androidx.compose.material3.TabIndicatorScope#tabIndicatorLayout(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function4<? super androidx.compose.ui.layout.MeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? super java.util.List<androidx.compose.material3.TabPosition>,? extends androidx.compose.ui.layout.MeasureResult>) parameter #0:
+    Removed parameter arg1 in androidx.compose.material3.TabIndicatorScope.tabIndicatorLayout(androidx.compose.ui.Modifier arg1, kotlin.jvm.functions.Function4<? super androidx.compose.ui.layout.MeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? super java.util.List<androidx.compose.material3.TabPosition>,? extends androidx.compose.ui.layout.MeasureResult> measure) from compatibility checked API surface
+BecameUnchecked: androidx.compose.material3.TabIndicatorScope#tabIndicatorLayout(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function4<? super androidx.compose.ui.layout.MeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? super java.util.List<androidx.compose.material3.TabPosition>,? extends androidx.compose.ui.layout.MeasureResult>) parameter #1:
+    Removed parameter measure in androidx.compose.material3.TabIndicatorScope.tabIndicatorLayout(androidx.compose.ui.Modifier arg1, kotlin.jvm.functions.Function4<? super androidx.compose.ui.layout.MeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? super java.util.List<androidx.compose.material3.TabPosition>,? extends androidx.compose.ui.layout.MeasureResult> measure) from compatibility checked API surface
+BecameUnchecked: androidx.compose.material3.TabIndicatorScope#tabIndicatorOffset(androidx.compose.ui.Modifier, int, boolean):
+    Removed method androidx.compose.material3.TabIndicatorScope.tabIndicatorOffset(androidx.compose.ui.Modifier,int,boolean) from compatibility checked API surface
+BecameUnchecked: androidx.compose.material3.TabIndicatorScope#tabIndicatorOffset(androidx.compose.ui.Modifier, int, boolean) parameter #0:
+    Removed parameter arg1 in androidx.compose.material3.TabIndicatorScope.tabIndicatorOffset(androidx.compose.ui.Modifier arg1, int selectedTabIndex, boolean matchContentSize) from compatibility checked API surface
+BecameUnchecked: androidx.compose.material3.TabIndicatorScope#tabIndicatorOffset(androidx.compose.ui.Modifier, int, boolean) parameter #1:
+    Removed parameter selectedTabIndex in androidx.compose.material3.TabIndicatorScope.tabIndicatorOffset(androidx.compose.ui.Modifier arg1, int selectedTabIndex, boolean matchContentSize) from compatibility checked API surface
+BecameUnchecked: androidx.compose.material3.TabIndicatorScope#tabIndicatorOffset(androidx.compose.ui.Modifier, int, boolean) parameter #2:
+    Removed parameter matchContentSize in androidx.compose.material3.TabIndicatorScope.tabIndicatorOffset(androidx.compose.ui.Modifier arg1, int selectedTabIndex, boolean matchContentSize) from compatibility checked API surface
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 7138834..4a436c2 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -1617,7 +1617,7 @@
     method @androidx.compose.runtime.Composable public static void Switch(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit>? onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? thumbContent, optional boolean enabled, optional androidx.compose.material3.SwitchColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
   }
 
-  public interface TabIndicatorScope {
+  @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public interface TabIndicatorScope {
     method public androidx.compose.ui.Modifier tabIndicatorLayout(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function4<? super androidx.compose.ui.layout.MeasureScope,? super androidx.compose.ui.layout.Measurable,? super androidx.compose.ui.unit.Constraints,? super java.util.List<androidx.compose.material3.TabPosition>,? extends androidx.compose.ui.layout.MeasureResult> measure);
     method public androidx.compose.ui.Modifier tabIndicatorOffset(androidx.compose.ui.Modifier, int selectedTabIndex, optional boolean matchContentSize);
   }
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TabSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TabSamples.kt
index 3ee638f..50aa382 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TabSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TabSamples.kt
@@ -467,6 +467,7 @@
     )
 }
 
+@OptIn(ExperimentalMaterial3Api::class)
 @Sampled
 @Composable
 fun TabIndicatorScope.FancyAnimatedIndicatorWithModifier(index: Int) {
diff --git a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
index 83b9163..d883f9e 100644
--- a/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
+++ b/compose/material3/material3/src/androidInstrumentedTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
@@ -376,11 +376,11 @@
                 )
             }
         }
-
+        rule.waitForIdle()
         screenHeightPx = with(rule.density) {
             rule.onNode(isPopup()).getUnclippedBoundsInRoot().height.toPx()
         }
-        assertThat(sheetState.currentValue).isEqualTo(SheetValue.PartiallyExpanded)
+        assertThat(sheetState.targetValue).isEqualTo(SheetValue.PartiallyExpanded)
         assertThat(sheetState.requireOffset())
             .isWithin(1f)
             .of(screenHeightPx / 2f)
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TabRow.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TabRow.kt
index e9c9f99..a251a76 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TabRow.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TabRow.kt
@@ -319,6 +319,7 @@
  * this can be used for more complex indicators requiring layout information about the tabs
  * like [TabRowDefaults.PrimaryIndicator] and [TabRowDefaults.SecondaryIndicator]
  */
+@ExperimentalMaterial3Api
 interface TabIndicatorScope {
 
     /**
@@ -353,6 +354,7 @@
     fun setTabPositions(positions: List<TabPosition>)
 }
 
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 private fun TabRowImpl(
     modifier: Modifier,
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/AutoboxingStateCreationDetector.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/AutoboxingStateCreationDetector.kt
index 4fc7275..b3763a5 100644
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/AutoboxingStateCreationDetector.kt
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/AutoboxingStateCreationDetector.kt
@@ -40,6 +40,7 @@
 import org.jetbrains.kotlin.psi.KtValueArgumentList
 import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
 import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.java.JavaUCallExpression
 import org.jetbrains.uast.skipParenthesizedExprDown
 
 /**
@@ -47,6 +48,12 @@
  * - a snapshot mutation policy argument is not specified (or it is structural equivalent policy)
  * - `T` is in the [replacements] map
  * - `T` is a non-nullable type
+ *
+ * This check only runs over Kotlin code, despite the possibility of calling mutableStateOf in
+ * Java. It's not possible to annotate a generic type in Java with @Nullable or @NonNull,
+ * so we will never have enough information to make the right call about whether you can make
+ * a suggested replacement or not. We therefore skip this check in all Java files to err on
+ * the side of underreporting.
  */
 class AutoboxingStateCreationDetector : Detector(), SourceCodeScanner {
 
@@ -64,6 +71,7 @@
     override fun getApplicableMethodNames() = listOf(Names.Runtime.MutableStateOf.shortName)
 
     override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+        if (node is JavaUCallExpression) return
         if (!method.isInPackageName(Names.Runtime.PackageName)) return
 
         val replacement = getSuggestedReplacementName(node) ?: return
diff --git a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/AutoboxingStateCreationDetectorTest.kt b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/AutoboxingStateCreationDetectorTest.kt
index 1c36c42..1eba489 100644
--- a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/AutoboxingStateCreationDetectorTest.kt
+++ b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/AutoboxingStateCreationDetectorTest.kt
@@ -33,6 +33,8 @@
 
     private val fqType = typeUnderTest.fqName
     private val type = typeUnderTest.typeName
+    private val jvmType = typeUnderTest.jvmClassName
+    private val fqJvmClass = typeUnderTest.fqJvmName
     private val stateValue = typeUnderTest.sampleValue
 
     private val primitiveStateStub = kotlin(
@@ -110,6 +112,41 @@
         )
     }
 
+    /**
+     * Regression test for b/314093514. Java doesn't allow specifying nullity of the generic type
+     * with either the AndroidX or JetBrains nullity annotations, so we never have enough
+     * information to know whether a mutableState created in Java is capable of being refactored
+     * into the specialized primitive version. Therefore, this inspection should never report
+     * for Java callers.
+     */
+    @Test
+    fun testTrivialMutableStateOf_notReportedInJava() {
+        lint().files(
+            primitiveStateStub,
+            Stubs.Composable,
+            Stubs.SnapshotState,
+            Stubs.StateFactoryMarker,
+            java(
+                """
+                    package androidx.compose.runtime.lint.test;
+
+                    import static androidx.compose.runtime.SnapshotStateKt.mutableStateOf;
+                    import static androidx.compose.runtime.SnapshotStateKt.structuralEqualityPolicy;
+
+                    import androidx.compose.runtime.*;
+                    import $fqJvmClass;
+
+                    class Test {
+                        public void valueAssignment() {
+                            MutableState<$jvmType> state = mutableStateOf($stateValue, structuralEqualityPolicy());
+                            state.setValue($stateValue);
+                        }
+                    }
+                """
+            )
+        ).run().expectClean()
+    }
+
     @Test
     fun testInferredMutableStateOf_thatCouldBeMutablePrimitiveStateOf() {
         lint().files(
@@ -488,27 +525,30 @@
         @JvmStatic
         @Parameterized.Parameters(name = "{0}")
         fun initParameters() = listOf(
-            testCase("kotlin.Int", "42"),
-            testCase("kotlin.Long", "0xABCDEF1234"),
-            testCase("kotlin.Float", "1.5f"),
-            testCase("kotlin.Double", "1.024")
+            testCase("kotlin.Int", "java.lang.Integer", "42"),
+            testCase("kotlin.Long", "java.lang.Long", "0xABCDEF1234"),
+            testCase("kotlin.Float", "java.lang.Float", "1.5f"),
+            testCase("kotlin.Double", "java.lang.Double", "1.024")
         )
 
-        private fun testCase(fqName: String, value: String): TypeUnderTest {
-            val parts = fqName.split('.')
-            return TypeUnderTest(
-                fqName = fqName,
-                typeName = parts.last(),
-                packageName = parts.dropLast(1).joinToString(separator = "."),
-                sampleValue = value
-            )
-        }
+        private fun testCase(
+            fqName: String,
+            jvmFqName: String,
+            value: String
+        ) = TypeUnderTest(
+            fqName = fqName,
+            typeName = fqName.split('.').last(),
+            fqJvmName = jvmFqName,
+            jvmClassName = jvmFqName.split('.').last(),
+            sampleValue = value
+        )
     }
 
     data class TypeUnderTest(
         val fqName: String,
         val typeName: String,
-        val packageName: String,
+        val fqJvmName: String,
+        val jvmClassName: String,
         val sampleValue: String,
     ) {
         // Formatting for test parameter list.
diff --git a/compose/ui/ui-test-junit4/api/current.ignore b/compose/ui/ui-test-junit4/api/current.ignore
index 8513023..c21570e 100644
--- a/compose/ui/ui-test-junit4/api/current.ignore
+++ b/compose/ui/ui-test-junit4/api/current.ignore
@@ -1,227 +1,5 @@
 // Baseline format: 1.0
-BecameUnchecked: Field AndroidComposeUiTest.activity:
-    Removed Field AndroidComposeUiTest.activity from compatibility checked API surface
-BecameUnchecked: Field AndroidComposeUiTestEnvironment.activity:
-    Removed Field AndroidComposeUiTestEnvironment.activity from compatibility checked API surface
-BecameUnchecked: Field AndroidComposeUiTestEnvironment.test:
-    Removed Field AndroidComposeUiTestEnvironment.test from compatibility checked API surface
-BecameUnchecked: Field ComposeUiTest.density:
-    Removed Field ComposeUiTest.density from compatibility checked API surface
-BecameUnchecked: Field ComposeUiTest.mainClock:
-    Removed Field ComposeUiTest.mainClock from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.AndroidComposeUiTest:
-    Removed class androidx.compose.ui.test.AndroidComposeUiTest from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.AndroidComposeUiTest#getActivity():
-    Removed method androidx.compose.ui.test.AndroidComposeUiTest.getActivity() from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.AndroidComposeUiTestEnvironment:
-    Removed class androidx.compose.ui.test.AndroidComposeUiTestEnvironment from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.AndroidComposeUiTestEnvironment#AndroidComposeUiTestEnvironment(kotlin.coroutines.CoroutineContext):
-    Removed constructor androidx.compose.ui.test.AndroidComposeUiTestEnvironment(kotlin.coroutines.CoroutineContext) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.AndroidComposeUiTestEnvironment#AndroidComposeUiTestEnvironment(kotlin.coroutines.CoroutineContext) parameter #0:
-    Removed parameter effectContext in androidx.compose.ui.test.AndroidComposeUiTestEnvironment(kotlin.coroutines.CoroutineContext effectContext) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.AndroidComposeUiTestEnvironment#getActivity():
-    Removed method androidx.compose.ui.test.AndroidComposeUiTestEnvironment.getActivity() from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.AndroidComposeUiTestEnvironment#getTest():
-    Removed method androidx.compose.ui.test.AndroidComposeUiTestEnvironment.getTest() from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.AndroidComposeUiTestEnvironment#runTest(kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,? extends R>):
-    Removed method androidx.compose.ui.test.AndroidComposeUiTestEnvironment.runTest(kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,? extends R>) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.AndroidComposeUiTestEnvironment#runTest(kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,? extends R>) parameter #0:
-    Removed parameter block in androidx.compose.ui.test.AndroidComposeUiTestEnvironment.runTest(kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,? extends R> block) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest:
-    Removed class androidx.compose.ui.test.ComposeUiTest from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest#awaitIdle(kotlin.coroutines.Continuation<? super kotlin.Unit>):
-    Removed method androidx.compose.ui.test.ComposeUiTest.awaitIdle(kotlin.coroutines.Continuation<? super kotlin.Unit>) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest#awaitIdle(kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #0:
-    Removed parameter arg1 in androidx.compose.ui.test.ComposeUiTest.awaitIdle(kotlin.coroutines.Continuation<? super kotlin.Unit> arg1) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest#getDensity():
-    Removed method androidx.compose.ui.test.ComposeUiTest.getDensity() from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest#getMainClock():
-    Removed method androidx.compose.ui.test.ComposeUiTest.getMainClock() from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest#registerIdlingResource(androidx.compose.ui.test.IdlingResource):
-    Removed method androidx.compose.ui.test.ComposeUiTest.registerIdlingResource(androidx.compose.ui.test.IdlingResource) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest#registerIdlingResource(androidx.compose.ui.test.IdlingResource) parameter #0:
-    Removed parameter idlingResource in androidx.compose.ui.test.ComposeUiTest.registerIdlingResource(androidx.compose.ui.test.IdlingResource idlingResource) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest#runOnIdle(kotlin.jvm.functions.Function0<? extends T>):
-    Removed method androidx.compose.ui.test.ComposeUiTest.runOnIdle(kotlin.jvm.functions.Function0<? extends T>) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest#runOnIdle(kotlin.jvm.functions.Function0<? extends T>) parameter #0:
-    Removed parameter action in androidx.compose.ui.test.ComposeUiTest.runOnIdle(kotlin.jvm.functions.Function0<? extends T> action) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest#runOnUiThread(kotlin.jvm.functions.Function0<? extends T>):
-    Removed method androidx.compose.ui.test.ComposeUiTest.runOnUiThread(kotlin.jvm.functions.Function0<? extends T>) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest#runOnUiThread(kotlin.jvm.functions.Function0<? extends T>) parameter #0:
-    Removed parameter action in androidx.compose.ui.test.ComposeUiTest.runOnUiThread(kotlin.jvm.functions.Function0<? extends T> action) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest#setContent(kotlin.jvm.functions.Function0<kotlin.Unit>):
-    Removed method androidx.compose.ui.test.ComposeUiTest.setContent(kotlin.jvm.functions.Function0<kotlin.Unit>) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest#setContent(kotlin.jvm.functions.Function0<kotlin.Unit>) parameter #0:
-    Removed parameter composable in androidx.compose.ui.test.ComposeUiTest.setContent(kotlin.jvm.functions.Function0<kotlin.Unit> composable) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest#unregisterIdlingResource(androidx.compose.ui.test.IdlingResource):
-    Removed method androidx.compose.ui.test.ComposeUiTest.unregisterIdlingResource(androidx.compose.ui.test.IdlingResource) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest#unregisterIdlingResource(androidx.compose.ui.test.IdlingResource) parameter #0:
-    Removed parameter idlingResource in androidx.compose.ui.test.ComposeUiTest.unregisterIdlingResource(androidx.compose.ui.test.IdlingResource idlingResource) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest#waitForIdle():
-    Removed method androidx.compose.ui.test.ComposeUiTest.waitForIdle() from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest#waitUntil(long, kotlin.jvm.functions.Function0<java.lang.Boolean>):
-    Removed method androidx.compose.ui.test.ComposeUiTest.waitUntil(long,kotlin.jvm.functions.Function0<java.lang.Boolean>) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest#waitUntil(long, kotlin.jvm.functions.Function0<java.lang.Boolean>) parameter #0:
-    Removed parameter timeoutMillis in androidx.compose.ui.test.ComposeUiTest.waitUntil(long timeoutMillis, kotlin.jvm.functions.Function0<java.lang.Boolean> condition) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest#waitUntil(long, kotlin.jvm.functions.Function0<java.lang.Boolean>) parameter #1:
-    Removed parameter condition in androidx.compose.ui.test.ComposeUiTest.waitUntil(long timeoutMillis, kotlin.jvm.functions.Function0<java.lang.Boolean> condition) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTestKt#runComposeUiTest(kotlin.coroutines.CoroutineContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit>):
-    Removed method androidx.compose.ui.test.ComposeUiTestKt.runComposeUiTest(kotlin.coroutines.CoroutineContext,kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit>) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTestKt#runComposeUiTest(kotlin.coroutines.CoroutineContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit>) parameter #0:
-    Removed parameter effectContext in androidx.compose.ui.test.ComposeUiTestKt.runComposeUiTest(kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit> block) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTestKt#runComposeUiTest(kotlin.coroutines.CoroutineContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit>) parameter #1:
-    Removed parameter block in androidx.compose.ui.test.ComposeUiTestKt.runComposeUiTest(kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit> block) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTestKt#waitUntilAtLeastOneExists(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher, long):
-    Removed method androidx.compose.ui.test.ComposeUiTestKt.waitUntilAtLeastOneExists(androidx.compose.ui.test.ComposeUiTest,androidx.compose.ui.test.SemanticsMatcher,long) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTestKt#waitUntilAtLeastOneExists(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher, long) parameter #0:
-    Removed parameter arg1 in androidx.compose.ui.test.ComposeUiTestKt.waitUntilAtLeastOneExists(androidx.compose.ui.test.ComposeUiTest arg1, androidx.compose.ui.test.SemanticsMatcher matcher, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTestKt#waitUntilAtLeastOneExists(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher, long) parameter #1:
-    Removed parameter matcher in androidx.compose.ui.test.ComposeUiTestKt.waitUntilAtLeastOneExists(androidx.compose.ui.test.ComposeUiTest arg1, androidx.compose.ui.test.SemanticsMatcher matcher, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTestKt#waitUntilAtLeastOneExists(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher, long) parameter #2:
-    Removed parameter timeoutMillis in androidx.compose.ui.test.ComposeUiTestKt.waitUntilAtLeastOneExists(androidx.compose.ui.test.ComposeUiTest arg1, androidx.compose.ui.test.SemanticsMatcher matcher, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTestKt#waitUntilDoesNotExist(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher, long):
-    Removed method androidx.compose.ui.test.ComposeUiTestKt.waitUntilDoesNotExist(androidx.compose.ui.test.ComposeUiTest,androidx.compose.ui.test.SemanticsMatcher,long) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTestKt#waitUntilDoesNotExist(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher, long) parameter #0:
-    Removed parameter arg1 in androidx.compose.ui.test.ComposeUiTestKt.waitUntilDoesNotExist(androidx.compose.ui.test.ComposeUiTest arg1, androidx.compose.ui.test.SemanticsMatcher matcher, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTestKt#waitUntilDoesNotExist(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher, long) parameter #1:
-    Removed parameter matcher in androidx.compose.ui.test.ComposeUiTestKt.waitUntilDoesNotExist(androidx.compose.ui.test.ComposeUiTest arg1, androidx.compose.ui.test.SemanticsMatcher matcher, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTestKt#waitUntilDoesNotExist(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher, long) parameter #2:
-    Removed parameter timeoutMillis in androidx.compose.ui.test.ComposeUiTestKt.waitUntilDoesNotExist(androidx.compose.ui.test.ComposeUiTest arg1, androidx.compose.ui.test.SemanticsMatcher matcher, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTestKt#waitUntilExactlyOneExists(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher, long):
-    Removed method androidx.compose.ui.test.ComposeUiTestKt.waitUntilExactlyOneExists(androidx.compose.ui.test.ComposeUiTest,androidx.compose.ui.test.SemanticsMatcher,long) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTestKt#waitUntilExactlyOneExists(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher, long) parameter #0:
-    Removed parameter arg1 in androidx.compose.ui.test.ComposeUiTestKt.waitUntilExactlyOneExists(androidx.compose.ui.test.ComposeUiTest arg1, androidx.compose.ui.test.SemanticsMatcher matcher, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTestKt#waitUntilExactlyOneExists(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher, long) parameter #1:
-    Removed parameter matcher in androidx.compose.ui.test.ComposeUiTestKt.waitUntilExactlyOneExists(androidx.compose.ui.test.ComposeUiTest arg1, androidx.compose.ui.test.SemanticsMatcher matcher, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTestKt#waitUntilExactlyOneExists(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher, long) parameter #2:
-    Removed parameter timeoutMillis in androidx.compose.ui.test.ComposeUiTestKt.waitUntilExactlyOneExists(androidx.compose.ui.test.ComposeUiTest arg1, androidx.compose.ui.test.SemanticsMatcher matcher, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTestKt#waitUntilNodeCount(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher, int, long):
-    Removed method androidx.compose.ui.test.ComposeUiTestKt.waitUntilNodeCount(androidx.compose.ui.test.ComposeUiTest,androidx.compose.ui.test.SemanticsMatcher,int,long) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTestKt#waitUntilNodeCount(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher, int, long) parameter #0:
-    Removed parameter arg1 in androidx.compose.ui.test.ComposeUiTestKt.waitUntilNodeCount(androidx.compose.ui.test.ComposeUiTest arg1, androidx.compose.ui.test.SemanticsMatcher matcher, int count, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTestKt#waitUntilNodeCount(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher, int, long) parameter #1:
-    Removed parameter matcher in androidx.compose.ui.test.ComposeUiTestKt.waitUntilNodeCount(androidx.compose.ui.test.ComposeUiTest arg1, androidx.compose.ui.test.SemanticsMatcher matcher, int count, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTestKt#waitUntilNodeCount(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher, int, long) parameter #2:
-    Removed parameter count in androidx.compose.ui.test.ComposeUiTestKt.waitUntilNodeCount(androidx.compose.ui.test.ComposeUiTest arg1, androidx.compose.ui.test.SemanticsMatcher matcher, int count, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTestKt#waitUntilNodeCount(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher, int, long) parameter #3:
-    Removed parameter timeoutMillis in androidx.compose.ui.test.ComposeUiTestKt.waitUntilNodeCount(androidx.compose.ui.test.ComposeUiTest arg1, androidx.compose.ui.test.SemanticsMatcher matcher, int count, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest_androidKt#AndroidComposeUiTestEnvironment(kotlin.coroutines.CoroutineContext, kotlin.jvm.functions.Function0<? extends A>):
-    Removed method androidx.compose.ui.test.ComposeUiTest_androidKt.AndroidComposeUiTestEnvironment(kotlin.coroutines.CoroutineContext,kotlin.jvm.functions.Function0<? extends A>) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest_androidKt#AndroidComposeUiTestEnvironment(kotlin.coroutines.CoroutineContext, kotlin.jvm.functions.Function0<? extends A>) parameter #0:
-    Removed parameter effectContext in androidx.compose.ui.test.ComposeUiTest_androidKt.AndroidComposeUiTestEnvironment(kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function0<? extends A> activityProvider) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest_androidKt#AndroidComposeUiTestEnvironment(kotlin.coroutines.CoroutineContext, kotlin.jvm.functions.Function0<? extends A>) parameter #1:
-    Removed parameter activityProvider in androidx.compose.ui.test.ComposeUiTest_androidKt.AndroidComposeUiTestEnvironment(kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function0<? extends A> activityProvider) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest_androidKt#runAndroidComposeUiTest(Class<A>, kotlin.coroutines.CoroutineContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,kotlin.Unit>):
-    Removed method androidx.compose.ui.test.ComposeUiTest_androidKt.runAndroidComposeUiTest(Class<A>,kotlin.coroutines.CoroutineContext,kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,kotlin.Unit>) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest_androidKt#runAndroidComposeUiTest(Class<A>, kotlin.coroutines.CoroutineContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,kotlin.Unit>) parameter #0:
-    Removed parameter activityClass in androidx.compose.ui.test.ComposeUiTest_androidKt.runAndroidComposeUiTest(Class<A> activityClass, kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,kotlin.Unit> block) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest_androidKt#runAndroidComposeUiTest(Class<A>, kotlin.coroutines.CoroutineContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,kotlin.Unit>) parameter #1:
-    Removed parameter effectContext in androidx.compose.ui.test.ComposeUiTest_androidKt.runAndroidComposeUiTest(Class<A> activityClass, kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,kotlin.Unit> block) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest_androidKt#runAndroidComposeUiTest(Class<A>, kotlin.coroutines.CoroutineContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,kotlin.Unit>) parameter #2:
-    Removed parameter block in androidx.compose.ui.test.ComposeUiTest_androidKt.runAndroidComposeUiTest(Class<A> activityClass, kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,kotlin.Unit> block) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest_androidKt#runAndroidComposeUiTest(kotlin.coroutines.CoroutineContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,kotlin.Unit>):
-    Removed method androidx.compose.ui.test.ComposeUiTest_androidKt.runAndroidComposeUiTest(kotlin.coroutines.CoroutineContext,kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,kotlin.Unit>) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest_androidKt#runAndroidComposeUiTest(kotlin.coroutines.CoroutineContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,kotlin.Unit>) parameter #0:
-    Removed parameter effectContext in androidx.compose.ui.test.ComposeUiTest_androidKt.runAndroidComposeUiTest(kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,kotlin.Unit> block) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest_androidKt#runAndroidComposeUiTest(kotlin.coroutines.CoroutineContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,kotlin.Unit>) parameter #1:
-    Removed parameter block in androidx.compose.ui.test.ComposeUiTest_androidKt.runAndroidComposeUiTest(kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,kotlin.Unit> block) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest_androidKt#runComposeUiTest(kotlin.coroutines.CoroutineContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit>):
-    Removed method androidx.compose.ui.test.ComposeUiTest_androidKt.runComposeUiTest(kotlin.coroutines.CoroutineContext,kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit>) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest_androidKt#runComposeUiTest(kotlin.coroutines.CoroutineContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit>) parameter #0:
-    Removed parameter effectContext in androidx.compose.ui.test.ComposeUiTest_androidKt.runComposeUiTest(kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit> block) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest_androidKt#runComposeUiTest(kotlin.coroutines.CoroutineContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit>) parameter #1:
-    Removed parameter block in androidx.compose.ui.test.ComposeUiTest_androidKt.runComposeUiTest(kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit> block) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest_androidKt#runEmptyComposeUiTest(kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit>):
-    Removed method androidx.compose.ui.test.ComposeUiTest_androidKt.runEmptyComposeUiTest(kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit>) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.ComposeUiTest_androidKt#runEmptyComposeUiTest(kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit>) parameter #0:
-    Removed parameter block in androidx.compose.ui.test.ComposeUiTest_androidKt.runEmptyComposeUiTest(kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit> block) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.StateRestorationTester:
-    Removed class androidx.compose.ui.test.StateRestorationTester from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.StateRestorationTester#StateRestorationTester(androidx.compose.ui.test.ComposeUiTest):
-    Removed constructor androidx.compose.ui.test.StateRestorationTester(androidx.compose.ui.test.ComposeUiTest) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.StateRestorationTester#StateRestorationTester(androidx.compose.ui.test.ComposeUiTest) parameter #0:
-    Removed parameter composeTest in androidx.compose.ui.test.StateRestorationTester(androidx.compose.ui.test.ComposeUiTest composeTest) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.StateRestorationTester#emulateSaveAndRestore():
-    Removed method androidx.compose.ui.test.StateRestorationTester.emulateSaveAndRestore() from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.StateRestorationTester#setContent(kotlin.jvm.functions.Function0<kotlin.Unit>):
-    Removed method androidx.compose.ui.test.StateRestorationTester.setContent(kotlin.jvm.functions.Function0<kotlin.Unit>) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.StateRestorationTester#setContent(kotlin.jvm.functions.Function0<kotlin.Unit>) parameter #0:
-    Removed parameter composable in androidx.compose.ui.test.StateRestorationTester.setContent(kotlin.jvm.functions.Function0<kotlin.Unit> composable) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule#AndroidComposeTestRule(R, kotlin.coroutines.CoroutineContext, kotlin.jvm.functions.Function1<? super R,? extends A>):
-    Removed constructor androidx.compose.ui.test.junit4.AndroidComposeTestRule(R,kotlin.coroutines.CoroutineContext,kotlin.jvm.functions.Function1<? super R,? extends A>) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule#AndroidComposeTestRule(R, kotlin.coroutines.CoroutineContext, kotlin.jvm.functions.Function1<? super R,? extends A>) parameter #0:
-    Removed parameter activityRule in androidx.compose.ui.test.junit4.AndroidComposeTestRule(R activityRule, kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super R,? extends A> activityProvider) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule#AndroidComposeTestRule(R, kotlin.coroutines.CoroutineContext, kotlin.jvm.functions.Function1<? super R,? extends A>) parameter #1:
-    Removed parameter effectContext in androidx.compose.ui.test.junit4.AndroidComposeTestRule(R activityRule, kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super R,? extends A> activityProvider) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule#AndroidComposeTestRule(R, kotlin.coroutines.CoroutineContext, kotlin.jvm.functions.Function1<? super R,? extends A>) parameter #2:
-    Removed parameter activityProvider in androidx.compose.ui.test.junit4.AndroidComposeTestRule(R activityRule, kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super R,? extends A> activityProvider) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule#waitUntilAtLeastOneExists(androidx.compose.ui.test.SemanticsMatcher, long):
-    Removed method androidx.compose.ui.test.junit4.AndroidComposeTestRule.waitUntilAtLeastOneExists(androidx.compose.ui.test.SemanticsMatcher,long) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule#waitUntilAtLeastOneExists(androidx.compose.ui.test.SemanticsMatcher, long) parameter #0:
-    Removed parameter matcher in androidx.compose.ui.test.junit4.AndroidComposeTestRule.waitUntilAtLeastOneExists(androidx.compose.ui.test.SemanticsMatcher matcher, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule#waitUntilAtLeastOneExists(androidx.compose.ui.test.SemanticsMatcher, long) parameter #1:
-    Removed parameter timeoutMillis in androidx.compose.ui.test.junit4.AndroidComposeTestRule.waitUntilAtLeastOneExists(androidx.compose.ui.test.SemanticsMatcher matcher, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule#waitUntilDoesNotExist(androidx.compose.ui.test.SemanticsMatcher, long):
-    Removed method androidx.compose.ui.test.junit4.AndroidComposeTestRule.waitUntilDoesNotExist(androidx.compose.ui.test.SemanticsMatcher,long) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule#waitUntilDoesNotExist(androidx.compose.ui.test.SemanticsMatcher, long) parameter #0:
-    Removed parameter matcher in androidx.compose.ui.test.junit4.AndroidComposeTestRule.waitUntilDoesNotExist(androidx.compose.ui.test.SemanticsMatcher matcher, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule#waitUntilDoesNotExist(androidx.compose.ui.test.SemanticsMatcher, long) parameter #1:
-    Removed parameter timeoutMillis in androidx.compose.ui.test.junit4.AndroidComposeTestRule.waitUntilDoesNotExist(androidx.compose.ui.test.SemanticsMatcher matcher, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule#waitUntilExactlyOneExists(androidx.compose.ui.test.SemanticsMatcher, long):
-    Removed method androidx.compose.ui.test.junit4.AndroidComposeTestRule.waitUntilExactlyOneExists(androidx.compose.ui.test.SemanticsMatcher,long) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule#waitUntilExactlyOneExists(androidx.compose.ui.test.SemanticsMatcher, long) parameter #0:
-    Removed parameter matcher in androidx.compose.ui.test.junit4.AndroidComposeTestRule.waitUntilExactlyOneExists(androidx.compose.ui.test.SemanticsMatcher matcher, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule#waitUntilExactlyOneExists(androidx.compose.ui.test.SemanticsMatcher, long) parameter #1:
-    Removed parameter timeoutMillis in androidx.compose.ui.test.junit4.AndroidComposeTestRule.waitUntilExactlyOneExists(androidx.compose.ui.test.SemanticsMatcher matcher, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule#waitUntilNodeCount(androidx.compose.ui.test.SemanticsMatcher, int, long):
-    Removed method androidx.compose.ui.test.junit4.AndroidComposeTestRule.waitUntilNodeCount(androidx.compose.ui.test.SemanticsMatcher,int,long) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule#waitUntilNodeCount(androidx.compose.ui.test.SemanticsMatcher, int, long) parameter #0:
-    Removed parameter matcher in androidx.compose.ui.test.junit4.AndroidComposeTestRule.waitUntilNodeCount(androidx.compose.ui.test.SemanticsMatcher matcher, int count, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule#waitUntilNodeCount(androidx.compose.ui.test.SemanticsMatcher, int, long) parameter #1:
-    Removed parameter count in androidx.compose.ui.test.junit4.AndroidComposeTestRule.waitUntilNodeCount(androidx.compose.ui.test.SemanticsMatcher matcher, int count, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule#waitUntilNodeCount(androidx.compose.ui.test.SemanticsMatcher, int, long) parameter #2:
-    Removed parameter timeoutMillis in androidx.compose.ui.test.junit4.AndroidComposeTestRule.waitUntilNodeCount(androidx.compose.ui.test.SemanticsMatcher matcher, int count, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule_androidKt#createAndroidComposeRule(Class<A>, kotlin.coroutines.CoroutineContext):
-    Removed method androidx.compose.ui.test.junit4.AndroidComposeTestRule_androidKt.createAndroidComposeRule(Class<A>,kotlin.coroutines.CoroutineContext) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule_androidKt#createAndroidComposeRule(Class<A>, kotlin.coroutines.CoroutineContext) parameter #0:
-    Removed parameter activityClass in androidx.compose.ui.test.junit4.AndroidComposeTestRule_androidKt.createAndroidComposeRule(Class<A> activityClass, kotlin.coroutines.CoroutineContext effectContext) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule_androidKt#createAndroidComposeRule(Class<A>, kotlin.coroutines.CoroutineContext) parameter #1:
-    Removed parameter effectContext in androidx.compose.ui.test.junit4.AndroidComposeTestRule_androidKt.createAndroidComposeRule(Class<A> activityClass, kotlin.coroutines.CoroutineContext effectContext) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule_androidKt#createAndroidComposeRule(kotlin.coroutines.CoroutineContext):
-    Removed method androidx.compose.ui.test.junit4.AndroidComposeTestRule_androidKt.createAndroidComposeRule(kotlin.coroutines.CoroutineContext) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule_androidKt#createAndroidComposeRule(kotlin.coroutines.CoroutineContext) parameter #0:
-    Removed parameter effectContext in androidx.compose.ui.test.junit4.AndroidComposeTestRule_androidKt.createAndroidComposeRule(kotlin.coroutines.CoroutineContext effectContext) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule_androidKt#createComposeRule(kotlin.coroutines.CoroutineContext):
-    Removed method androidx.compose.ui.test.junit4.AndroidComposeTestRule_androidKt.createComposeRule(kotlin.coroutines.CoroutineContext) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule_androidKt#createComposeRule(kotlin.coroutines.CoroutineContext) parameter #0:
-    Removed parameter effectContext in androidx.compose.ui.test.junit4.AndroidComposeTestRule_androidKt.createComposeRule(kotlin.coroutines.CoroutineContext effectContext) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule_androidKt#createEmptyComposeRule(kotlin.coroutines.CoroutineContext):
-    Removed method androidx.compose.ui.test.junit4.AndroidComposeTestRule_androidKt.createEmptyComposeRule(kotlin.coroutines.CoroutineContext) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.AndroidComposeTestRule_androidKt#createEmptyComposeRule(kotlin.coroutines.CoroutineContext) parameter #0:
-    Removed parameter effectContext in androidx.compose.ui.test.junit4.AndroidComposeTestRule_androidKt.createEmptyComposeRule(kotlin.coroutines.CoroutineContext effectContext) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.ComposeTestRule#waitUntilAtLeastOneExists(androidx.compose.ui.test.SemanticsMatcher, long):
-    Removed method androidx.compose.ui.test.junit4.ComposeTestRule.waitUntilAtLeastOneExists(androidx.compose.ui.test.SemanticsMatcher,long) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.ComposeTestRule#waitUntilAtLeastOneExists(androidx.compose.ui.test.SemanticsMatcher, long) parameter #0:
-    Removed parameter matcher in androidx.compose.ui.test.junit4.ComposeTestRule.waitUntilAtLeastOneExists(androidx.compose.ui.test.SemanticsMatcher matcher, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.ComposeTestRule#waitUntilAtLeastOneExists(androidx.compose.ui.test.SemanticsMatcher, long) parameter #1:
-    Removed parameter timeoutMillis in androidx.compose.ui.test.junit4.ComposeTestRule.waitUntilAtLeastOneExists(androidx.compose.ui.test.SemanticsMatcher matcher, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.ComposeTestRule#waitUntilDoesNotExist(androidx.compose.ui.test.SemanticsMatcher, long):
-    Removed method androidx.compose.ui.test.junit4.ComposeTestRule.waitUntilDoesNotExist(androidx.compose.ui.test.SemanticsMatcher,long) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.ComposeTestRule#waitUntilDoesNotExist(androidx.compose.ui.test.SemanticsMatcher, long) parameter #0:
-    Removed parameter matcher in androidx.compose.ui.test.junit4.ComposeTestRule.waitUntilDoesNotExist(androidx.compose.ui.test.SemanticsMatcher matcher, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.ComposeTestRule#waitUntilDoesNotExist(androidx.compose.ui.test.SemanticsMatcher, long) parameter #1:
-    Removed parameter timeoutMillis in androidx.compose.ui.test.junit4.ComposeTestRule.waitUntilDoesNotExist(androidx.compose.ui.test.SemanticsMatcher matcher, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.ComposeTestRule#waitUntilExactlyOneExists(androidx.compose.ui.test.SemanticsMatcher, long):
-    Removed method androidx.compose.ui.test.junit4.ComposeTestRule.waitUntilExactlyOneExists(androidx.compose.ui.test.SemanticsMatcher,long) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.ComposeTestRule#waitUntilExactlyOneExists(androidx.compose.ui.test.SemanticsMatcher, long) parameter #0:
-    Removed parameter matcher in androidx.compose.ui.test.junit4.ComposeTestRule.waitUntilExactlyOneExists(androidx.compose.ui.test.SemanticsMatcher matcher, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.ComposeTestRule#waitUntilExactlyOneExists(androidx.compose.ui.test.SemanticsMatcher, long) parameter #1:
-    Removed parameter timeoutMillis in androidx.compose.ui.test.junit4.ComposeTestRule.waitUntilExactlyOneExists(androidx.compose.ui.test.SemanticsMatcher matcher, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.ComposeTestRule#waitUntilNodeCount(androidx.compose.ui.test.SemanticsMatcher, int, long):
-    Removed method androidx.compose.ui.test.junit4.ComposeTestRule.waitUntilNodeCount(androidx.compose.ui.test.SemanticsMatcher,int,long) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.ComposeTestRule#waitUntilNodeCount(androidx.compose.ui.test.SemanticsMatcher, int, long) parameter #0:
-    Removed parameter matcher in androidx.compose.ui.test.junit4.ComposeTestRule.waitUntilNodeCount(androidx.compose.ui.test.SemanticsMatcher matcher, int count, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.ComposeTestRule#waitUntilNodeCount(androidx.compose.ui.test.SemanticsMatcher, int, long) parameter #1:
-    Removed parameter count in androidx.compose.ui.test.junit4.ComposeTestRule.waitUntilNodeCount(androidx.compose.ui.test.SemanticsMatcher matcher, int count, long timeoutMillis) from compatibility checked API surface
-BecameUnchecked: androidx.compose.ui.test.junit4.ComposeTestRule#waitUntilNodeCount(androidx.compose.ui.test.SemanticsMatcher, int, long) parameter #2:
-    Removed parameter timeoutMillis in androidx.compose.ui.test.junit4.ComposeTestRule.waitUntilNodeCount(androidx.compose.ui.test.SemanticsMatcher matcher, int count, long timeoutMillis) from compatibility checked API surface
+RemovedPackage: androidx.compose.ui.test:
+    Removed package androidx.compose.ui.test
+RemovedPackage: androidx.compose.ui.test.junit4.android:
+    Removed package androidx.compose.ui.test.junit4.android
diff --git a/compose/ui/ui-test-junit4/api/current.txt b/compose/ui/ui-test-junit4/api/current.txt
index 06d2062..8f15c3f 100644
--- a/compose/ui/ui-test-junit4/api/current.txt
+++ b/compose/ui/ui-test-junit4/api/current.txt
@@ -1,59 +1,4 @@
 // Signature format: 4.0
-package androidx.compose.ui.test {
-
-  @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public sealed interface AndroidComposeUiTest<A extends androidx.activity.ComponentActivity> extends androidx.compose.ui.test.ComposeUiTest {
-    method public A? getActivity();
-    property public abstract A? activity;
-  }
-
-  @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public abstract class AndroidComposeUiTestEnvironment<A extends androidx.activity.ComponentActivity> {
-    ctor public AndroidComposeUiTestEnvironment(optional kotlin.coroutines.CoroutineContext effectContext);
-    method protected abstract A? getActivity();
-    method public final androidx.compose.ui.test.AndroidComposeUiTest<A> getTest();
-    method public final <R> R runTest(kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,? extends R> block);
-    property protected abstract A? activity;
-    property public final androidx.compose.ui.test.AndroidComposeUiTest<A> test;
-  }
-
-  @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public sealed interface ComposeUiTest extends androidx.compose.ui.test.SemanticsNodeInteractionsProvider {
-    method public suspend Object? awaitIdle(kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method public androidx.compose.ui.unit.Density getDensity();
-    method public androidx.compose.ui.test.MainTestClock getMainClock();
-    method public void registerIdlingResource(androidx.compose.ui.test.IdlingResource idlingResource);
-    method public <T> T runOnIdle(kotlin.jvm.functions.Function0<? extends T> action);
-    method public <T> T runOnUiThread(kotlin.jvm.functions.Function0<? extends T> action);
-    method public void setContent(kotlin.jvm.functions.Function0<kotlin.Unit> composable);
-    method public void unregisterIdlingResource(androidx.compose.ui.test.IdlingResource idlingResource);
-    method public void waitForIdle();
-    method public void waitUntil(optional long timeoutMillis, kotlin.jvm.functions.Function0<java.lang.Boolean> condition);
-    property public abstract androidx.compose.ui.unit.Density density;
-    property public abstract androidx.compose.ui.test.MainTestClock mainClock;
-  }
-
-  public final class ComposeUiTestKt {
-    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void runComposeUiTest(optional kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit> block);
-    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void waitUntilAtLeastOneExists(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher matcher, optional long timeoutMillis);
-    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void waitUntilDoesNotExist(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher matcher, optional long timeoutMillis);
-    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void waitUntilExactlyOneExists(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher matcher, optional long timeoutMillis);
-    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void waitUntilNodeCount(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher matcher, int count, optional long timeoutMillis);
-  }
-
-  public final class ComposeUiTest_androidKt {
-    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static inline <A extends androidx.activity.ComponentActivity> androidx.compose.ui.test.AndroidComposeUiTestEnvironment<A> AndroidComposeUiTestEnvironment(optional kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function0<? extends A> activityProvider);
-    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static <A extends androidx.activity.ComponentActivity> void runAndroidComposeUiTest(Class<A> activityClass, optional kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,kotlin.Unit> block);
-    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static inline <reified A extends androidx.activity.ComponentActivity> void runAndroidComposeUiTest(optional kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,kotlin.Unit> block);
-    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void runComposeUiTest(optional kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit> block);
-    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void runEmptyComposeUiTest(kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit> block);
-  }
-
-  @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public final class StateRestorationTester {
-    ctor public StateRestorationTester(androidx.compose.ui.test.ComposeUiTest composeTest);
-    method public void emulateSaveAndRestore();
-    method public void setContent(kotlin.jvm.functions.Function0<kotlin.Unit> composable);
-  }
-
-}
-
 package androidx.compose.ui.test.junit4 {
 
   public final class AndroidComposeTestRule<R extends org.junit.rules.TestRule, A extends androidx.activity.ComponentActivity> implements androidx.compose.ui.test.junit4.ComposeContentTestRule {
@@ -130,11 +75,3 @@
 
 }
 
-package androidx.compose.ui.test.junit4.android {
-
-  public final class ComposeNotIdleException extends java.lang.Exception {
-    ctor public ComposeNotIdleException(String? message, Throwable? cause);
-  }
-
-}
-
diff --git a/compose/ui/ui-test-junit4/api/restricted_current.ignore b/compose/ui/ui-test-junit4/api/restricted_current.ignore
new file mode 100644
index 0000000..c21570e
--- /dev/null
+++ b/compose/ui/ui-test-junit4/api/restricted_current.ignore
@@ -0,0 +1,5 @@
+// Baseline format: 1.0
+RemovedPackage: androidx.compose.ui.test:
+    Removed package androidx.compose.ui.test
+RemovedPackage: androidx.compose.ui.test.junit4.android:
+    Removed package androidx.compose.ui.test.junit4.android
diff --git a/compose/ui/ui-test-junit4/api/restricted_current.txt b/compose/ui/ui-test-junit4/api/restricted_current.txt
index 06d2062..8f15c3f 100644
--- a/compose/ui/ui-test-junit4/api/restricted_current.txt
+++ b/compose/ui/ui-test-junit4/api/restricted_current.txt
@@ -1,59 +1,4 @@
 // Signature format: 4.0
-package androidx.compose.ui.test {
-
-  @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public sealed interface AndroidComposeUiTest<A extends androidx.activity.ComponentActivity> extends androidx.compose.ui.test.ComposeUiTest {
-    method public A? getActivity();
-    property public abstract A? activity;
-  }
-
-  @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public abstract class AndroidComposeUiTestEnvironment<A extends androidx.activity.ComponentActivity> {
-    ctor public AndroidComposeUiTestEnvironment(optional kotlin.coroutines.CoroutineContext effectContext);
-    method protected abstract A? getActivity();
-    method public final androidx.compose.ui.test.AndroidComposeUiTest<A> getTest();
-    method public final <R> R runTest(kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,? extends R> block);
-    property protected abstract A? activity;
-    property public final androidx.compose.ui.test.AndroidComposeUiTest<A> test;
-  }
-
-  @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public sealed interface ComposeUiTest extends androidx.compose.ui.test.SemanticsNodeInteractionsProvider {
-    method public suspend Object? awaitIdle(kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method public androidx.compose.ui.unit.Density getDensity();
-    method public androidx.compose.ui.test.MainTestClock getMainClock();
-    method public void registerIdlingResource(androidx.compose.ui.test.IdlingResource idlingResource);
-    method public <T> T runOnIdle(kotlin.jvm.functions.Function0<? extends T> action);
-    method public <T> T runOnUiThread(kotlin.jvm.functions.Function0<? extends T> action);
-    method public void setContent(kotlin.jvm.functions.Function0<kotlin.Unit> composable);
-    method public void unregisterIdlingResource(androidx.compose.ui.test.IdlingResource idlingResource);
-    method public void waitForIdle();
-    method public void waitUntil(optional long timeoutMillis, kotlin.jvm.functions.Function0<java.lang.Boolean> condition);
-    property public abstract androidx.compose.ui.unit.Density density;
-    property public abstract androidx.compose.ui.test.MainTestClock mainClock;
-  }
-
-  public final class ComposeUiTestKt {
-    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void runComposeUiTest(optional kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit> block);
-    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void waitUntilAtLeastOneExists(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher matcher, optional long timeoutMillis);
-    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void waitUntilDoesNotExist(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher matcher, optional long timeoutMillis);
-    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void waitUntilExactlyOneExists(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher matcher, optional long timeoutMillis);
-    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void waitUntilNodeCount(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher matcher, int count, optional long timeoutMillis);
-  }
-
-  public final class ComposeUiTest_androidKt {
-    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static inline <A extends androidx.activity.ComponentActivity> androidx.compose.ui.test.AndroidComposeUiTestEnvironment<A> AndroidComposeUiTestEnvironment(optional kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function0<? extends A> activityProvider);
-    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static <A extends androidx.activity.ComponentActivity> void runAndroidComposeUiTest(Class<A> activityClass, optional kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,kotlin.Unit> block);
-    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static inline <reified A extends androidx.activity.ComponentActivity> void runAndroidComposeUiTest(optional kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,kotlin.Unit> block);
-    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void runComposeUiTest(optional kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit> block);
-    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void runEmptyComposeUiTest(kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit> block);
-  }
-
-  @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public final class StateRestorationTester {
-    ctor public StateRestorationTester(androidx.compose.ui.test.ComposeUiTest composeTest);
-    method public void emulateSaveAndRestore();
-    method public void setContent(kotlin.jvm.functions.Function0<kotlin.Unit> composable);
-  }
-
-}
-
 package androidx.compose.ui.test.junit4 {
 
   public final class AndroidComposeTestRule<R extends org.junit.rules.TestRule, A extends androidx.activity.ComponentActivity> implements androidx.compose.ui.test.junit4.ComposeContentTestRule {
@@ -130,11 +75,3 @@
 
 }
 
-package androidx.compose.ui.test.junit4.android {
-
-  public final class ComposeNotIdleException extends java.lang.Exception {
-    ctor public ComposeNotIdleException(String? message, Throwable? cause);
-  }
-
-}
-
diff --git a/compose/ui/ui-test-junit4/build.gradle b/compose/ui/ui-test-junit4/build.gradle
index 668b67d..f185596 100644
--- a/compose/ui/ui-test-junit4/build.gradle
+++ b/compose/ui/ui-test-junit4/build.gradle
@@ -69,8 +69,6 @@
                 implementation("androidx.lifecycle:lifecycle-runtime:2.5.1")
                 implementation("androidx.test:core:1.5.0")
                 implementation("androidx.test:monitor:1.6.1")
-		        implementation("androidx.test.espresso:espresso-core:3.3.0")
-                implementation("androidx.test.espresso:espresso-idling-resource:3.5.0")
             }
         }
 
diff --git a/compose/ui/ui-test-junit4/lint-baseline.xml b/compose/ui/ui-test-junit4/lint-baseline.xml
deleted file mode 100644
index 5b697f0..0000000
--- a/compose/ui/ui-test-junit4/lint-baseline.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-beta05" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta05)" variant="all" version="8.1.0-beta05">
-
-    <issue
-        id="BanThreadSleep"
-        message="Uses Thread.sleep()"
-        errorLine1="                Thread.sleep(10)"
-        errorLine2="                       ~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/test/ComposeUiTest.android.kt"/>
-    </issue>
-
-</issues>
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/AndroidManifest.xml b/compose/ui/ui-test-junit4/src/androidInstrumentedTest/AndroidManifest.xml
deleted file mode 100644
index 2cc9c44..0000000
--- a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/AndroidManifest.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright 2018 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
-    <application>
-        <activity android:name="androidx.compose.ui.test.junit4.CustomActivity"
-            android:theme="@android:style/Theme.Material.NoActionBar.Fullscreen" />
-        <activity android:name="androidx.compose.ui.test.junit4.MultipleActivitiesFindTest$Activity1" />
-        <activity android:name="androidx.compose.ui.test.junit4.MultipleActivitiesFindTest$Activity2" />
-        <activity android:name="androidx.compose.ui.test.junit4.MultipleActivitiesClickTest$Activity1" />
-        <activity android:name="androidx.compose.ui.test.junit4.MultipleActivitiesClickTest$Activity2" />
-        <activity android:name="androidx.compose.ui.test.junit4.MultipleActivitiesFirstDrawTest$Activity1" />
-        <activity android:name="androidx.compose.ui.test.junit4.MultipleActivitiesFirstDrawTest$Activity2" />
-        <activity android:name="androidx.compose.ui.test.junit4.LateSetContentTest$Activity" />
-        <activity android:name="androidx.compose.ui.test.junit4.MultipleActivitiesWithoutComposeTest$Activity1" />
-        <activity android:name="androidx.compose.ui.test.junit4.MultipleActivitiesWithoutComposeTest$Activity2" />
-        <activity android:name="androidx.compose.ui.test.junit4.MultipleActivitiesWithoutComposeTest$Activity3" />
-    </application>
-</manifest>
diff --git a/compose/ui/ui-test-junit4/src/desktopTest/kotlin/androidx/compose/ui/test/DesktopComposeUiTestTest.kt b/compose/ui/ui-test-junit4/src/desktopTest/kotlin/androidx/compose/ui/test/DesktopComposeUiTestTest.kt
new file mode 100644
index 0000000..1f55a51
--- /dev/null
+++ b/compose/ui/ui-test-junit4/src/desktopTest/kotlin/androidx/compose/ui/test/DesktopComposeUiTestTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.test
+
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.test.junit4.createComposeRule
+import com.google.common.truth.Truth.assertThat
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.runners.model.Statement
+
+@RunWith(JUnit4::class)
+@OptIn(ExperimentalTestApi::class)
+class DesktopComposeUiTestTest {
+
+    private lateinit var testDescription: Description
+
+    /**
+     * Records the current [testDescription] for tests that need to invoke the compose test rule
+     * directly.
+     */
+    @get:Rule
+    val testWatcher = object : TestWatcher() {
+        override fun starting(description: Description) {
+            testDescription = description
+        }
+    }
+
+    @Test
+    fun effectContextPropagatedToComposition_createComposeRule() {
+        val testElement = TestCoroutineContextElement()
+        lateinit var compositionScope: CoroutineScope
+        val rule = createComposeRule(testElement)
+        val baseStatement = object : Statement() {
+            override fun evaluate() {
+                rule.setContent {
+                    compositionScope = rememberCoroutineScope()
+                }
+                rule.waitForIdle()
+            }
+        }
+        rule.apply(baseStatement, testDescription)
+            .evaluate()
+
+        val elementFromComposition =
+            compositionScope.coroutineContext[TestCoroutineContextElement]
+        assertThat(elementFromComposition).isSameInstanceAs(testElement)
+    }
+
+    private class TestCoroutineContextElement : CoroutineContext.Element {
+        override val key: CoroutineContext.Key<*> get() = Key
+
+        companion object Key : CoroutineContext.Key<TestCoroutineContextElement>
+    }
+}
diff --git a/compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/ComposeTestRule.jvm.kt b/compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/ComposeTestRule.jvm.kt
index 08dadac..9ee2023 100644
--- a/compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/ComposeTestRule.jvm.kt
+++ b/compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/ComposeTestRule.jvm.kt
@@ -17,7 +17,6 @@
 package androidx.compose.ui.test.junit4
 
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.test.ComposeTimeoutException
 import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.IdlingResource
 import androidx.compose.ui.test.MainTestClock
diff --git a/compose/ui/ui-test/api/current.txt b/compose/ui/ui-test/api/current.txt
index 666a657..ad2a32d 100644
--- a/compose/ui/ui-test/api/current.txt
+++ b/compose/ui/ui-test/api/current.txt
@@ -22,6 +22,20 @@
     method public static androidx.compose.ui.test.SemanticsNodeInteraction requestFocus(androidx.compose.ui.test.SemanticsNodeInteraction);
   }
 
+  @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public sealed interface AndroidComposeUiTest<A extends androidx.activity.ComponentActivity> extends androidx.compose.ui.test.ComposeUiTest {
+    method public A? getActivity();
+    property public abstract A? activity;
+  }
+
+  @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public abstract class AndroidComposeUiTestEnvironment<A extends androidx.activity.ComponentActivity> {
+    ctor public AndroidComposeUiTestEnvironment(optional kotlin.coroutines.CoroutineContext effectContext);
+    method protected abstract A? getActivity();
+    method public final androidx.compose.ui.test.AndroidComposeUiTest<A> getTest();
+    method public final <R> R runTest(kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,? extends R> block);
+    property protected abstract A? activity;
+    property public final androidx.compose.ui.test.AndroidComposeUiTest<A> test;
+  }
+
   public final class AndroidImageHelpers_androidKt {
     method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.compose.ui.graphics.ImageBitmap captureToImage(androidx.compose.ui.test.SemanticsNodeInteraction);
   }
@@ -75,6 +89,37 @@
     ctor public ComposeTimeoutException(String? message);
   }
 
+  @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public sealed interface ComposeUiTest extends androidx.compose.ui.test.SemanticsNodeInteractionsProvider {
+    method public suspend Object? awaitIdle(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public androidx.compose.ui.unit.Density getDensity();
+    method public androidx.compose.ui.test.MainTestClock getMainClock();
+    method public void registerIdlingResource(androidx.compose.ui.test.IdlingResource idlingResource);
+    method public <T> T runOnIdle(kotlin.jvm.functions.Function0<? extends T> action);
+    method public <T> T runOnUiThread(kotlin.jvm.functions.Function0<? extends T> action);
+    method public void setContent(kotlin.jvm.functions.Function0<kotlin.Unit> composable);
+    method public void unregisterIdlingResource(androidx.compose.ui.test.IdlingResource idlingResource);
+    method public void waitForIdle();
+    method public void waitUntil(optional long timeoutMillis, kotlin.jvm.functions.Function0<java.lang.Boolean> condition);
+    property public abstract androidx.compose.ui.unit.Density density;
+    property public abstract androidx.compose.ui.test.MainTestClock mainClock;
+  }
+
+  public final class ComposeUiTestKt {
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void runComposeUiTest(optional kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit> block);
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void waitUntilAtLeastOneExists(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher matcher, optional long timeoutMillis);
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void waitUntilDoesNotExist(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher matcher, optional long timeoutMillis);
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void waitUntilExactlyOneExists(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher matcher, optional long timeoutMillis);
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void waitUntilNodeCount(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher matcher, int count, optional long timeoutMillis);
+  }
+
+  public final class ComposeUiTest_androidKt {
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static inline <A extends androidx.activity.ComponentActivity> androidx.compose.ui.test.AndroidComposeUiTestEnvironment<A> AndroidComposeUiTestEnvironment(optional kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function0<? extends A> activityProvider);
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static <A extends androidx.activity.ComponentActivity> void runAndroidComposeUiTest(Class<A> activityClass, optional kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,kotlin.Unit> block);
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static inline <reified A extends androidx.activity.ComponentActivity> void runAndroidComposeUiTest(optional kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,kotlin.Unit> block);
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void runComposeUiTest(optional kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit> block);
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void runEmptyComposeUiTest(kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit> block);
+  }
+
   @SuppressCompatibility @kotlin.RequiresOptIn(message="This testing API is experimental and is likely to be changed or removed entirely") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalTestApi {
   }
 
@@ -434,6 +479,12 @@
     property public final String description;
   }
 
+  @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public final class StateRestorationTester {
+    ctor public StateRestorationTester(androidx.compose.ui.test.ComposeUiTest composeTest);
+    method public void emulateSaveAndRestore();
+    method public void setContent(kotlin.jvm.functions.Function0<kotlin.Unit> composable);
+  }
+
   public final class TestContext {
   }
 
@@ -513,3 +564,11 @@
 
 }
 
+package androidx.compose.ui.test.junit4.android {
+
+  public final class ComposeNotIdleException extends java.lang.Exception {
+    ctor public ComposeNotIdleException(String? message, Throwable? cause);
+  }
+
+}
+
diff --git a/compose/ui/ui-test/api/restricted_current.txt b/compose/ui/ui-test/api/restricted_current.txt
index b8f4e37..9f051b8 100644
--- a/compose/ui/ui-test/api/restricted_current.txt
+++ b/compose/ui/ui-test/api/restricted_current.txt
@@ -22,6 +22,20 @@
     method public static androidx.compose.ui.test.SemanticsNodeInteraction requestFocus(androidx.compose.ui.test.SemanticsNodeInteraction);
   }
 
+  @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public sealed interface AndroidComposeUiTest<A extends androidx.activity.ComponentActivity> extends androidx.compose.ui.test.ComposeUiTest {
+    method public A? getActivity();
+    property public abstract A? activity;
+  }
+
+  @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public abstract class AndroidComposeUiTestEnvironment<A extends androidx.activity.ComponentActivity> {
+    ctor public AndroidComposeUiTestEnvironment(optional kotlin.coroutines.CoroutineContext effectContext);
+    method protected abstract A? getActivity();
+    method public final androidx.compose.ui.test.AndroidComposeUiTest<A> getTest();
+    method public final <R> R runTest(kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,? extends R> block);
+    property protected abstract A? activity;
+    property public final androidx.compose.ui.test.AndroidComposeUiTest<A> test;
+  }
+
   public final class AndroidImageHelpers_androidKt {
     method @RequiresApi(android.os.Build.VERSION_CODES.O) public static androidx.compose.ui.graphics.ImageBitmap captureToImage(androidx.compose.ui.test.SemanticsNodeInteraction);
   }
@@ -75,6 +89,37 @@
     ctor public ComposeTimeoutException(String? message);
   }
 
+  @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public sealed interface ComposeUiTest extends androidx.compose.ui.test.SemanticsNodeInteractionsProvider {
+    method public suspend Object? awaitIdle(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public androidx.compose.ui.unit.Density getDensity();
+    method public androidx.compose.ui.test.MainTestClock getMainClock();
+    method public void registerIdlingResource(androidx.compose.ui.test.IdlingResource idlingResource);
+    method public <T> T runOnIdle(kotlin.jvm.functions.Function0<? extends T> action);
+    method public <T> T runOnUiThread(kotlin.jvm.functions.Function0<? extends T> action);
+    method public void setContent(kotlin.jvm.functions.Function0<kotlin.Unit> composable);
+    method public void unregisterIdlingResource(androidx.compose.ui.test.IdlingResource idlingResource);
+    method public void waitForIdle();
+    method public void waitUntil(optional long timeoutMillis, kotlin.jvm.functions.Function0<java.lang.Boolean> condition);
+    property public abstract androidx.compose.ui.unit.Density density;
+    property public abstract androidx.compose.ui.test.MainTestClock mainClock;
+  }
+
+  public final class ComposeUiTestKt {
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void runComposeUiTest(optional kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit> block);
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void waitUntilAtLeastOneExists(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher matcher, optional long timeoutMillis);
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void waitUntilDoesNotExist(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher matcher, optional long timeoutMillis);
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void waitUntilExactlyOneExists(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher matcher, optional long timeoutMillis);
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void waitUntilNodeCount(androidx.compose.ui.test.ComposeUiTest, androidx.compose.ui.test.SemanticsMatcher matcher, int count, optional long timeoutMillis);
+  }
+
+  public final class ComposeUiTest_androidKt {
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static inline <A extends androidx.activity.ComponentActivity> androidx.compose.ui.test.AndroidComposeUiTestEnvironment<A> AndroidComposeUiTestEnvironment(optional kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function0<? extends A> activityProvider);
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static <A extends androidx.activity.ComponentActivity> void runAndroidComposeUiTest(Class<A> activityClass, optional kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,kotlin.Unit> block);
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static inline <reified A extends androidx.activity.ComponentActivity> void runAndroidComposeUiTest(optional kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.AndroidComposeUiTest<A>,kotlin.Unit> block);
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void runComposeUiTest(optional kotlin.coroutines.CoroutineContext effectContext, kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit> block);
+    method @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public static void runEmptyComposeUiTest(kotlin.jvm.functions.Function1<? super androidx.compose.ui.test.ComposeUiTest,kotlin.Unit> block);
+  }
+
   @SuppressCompatibility @kotlin.RequiresOptIn(message="This testing API is experimental and is likely to be changed or removed entirely") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalTestApi {
   }
 
@@ -435,6 +480,12 @@
     property public final String description;
   }
 
+  @SuppressCompatibility @androidx.compose.ui.test.ExperimentalTestApi public final class StateRestorationTester {
+    ctor public StateRestorationTester(androidx.compose.ui.test.ComposeUiTest composeTest);
+    method public void emulateSaveAndRestore();
+    method public void setContent(kotlin.jvm.functions.Function0<kotlin.Unit> composable);
+  }
+
   public final class TestContext {
   }
 
@@ -514,3 +565,11 @@
 
 }
 
+package androidx.compose.ui.test.junit4.android {
+
+  public final class ComposeNotIdleException extends java.lang.Exception {
+    ctor public ComposeNotIdleException(String? message, Throwable? cause);
+  }
+
+}
+
diff --git a/compose/ui/ui-test/build.gradle b/compose/ui/ui-test/build.gradle
index d2d0338..3e0c6df 100644
--- a/compose/ui/ui-test/build.gradle
+++ b/compose/ui/ui-test/build.gradle
@@ -60,11 +60,14 @@
         androidMain {
             dependsOn(jvmMain)
             dependencies {
+                implementation "androidx.activity:activity-compose:1.3.0"
+
                 api(project(":compose:ui:ui-graphics"))
 
                 implementation("androidx.annotation:annotation:1.1.0")
                 implementation("androidx.core:core-ktx:1.2.0")
                 implementation("androidx.test.espresso:espresso-core:3.5.0")
+                implementation("androidx.test.espresso:espresso-idling-resource:3.5.0")
             	implementation("androidx.test:monitor:1.6.1")
 	        }
         }
@@ -96,12 +99,19 @@
             dependsOn(androidCommonTest)
             dependencies {
                 implementation(project(":compose:material:material"))
-                implementation(project(":compose:ui:ui-test-junit4"))
+                implementation(project(":compose:animation:animation"))
+                implementation(project(":compose:ui:ui-test"))
                 implementation("androidx.activity:activity-compose:1.3.1")
+                implementation("androidx.fragment:fragment-testing:1.4.1")
+
                 implementation(libs.mockitoCore)
                 implementation(libs.mockitoKotlin)
                 implementation(libs.dexmakerMockito)
                 implementation(libs.kotlinTest)
+                implementation(libs.testRules)
+                implementation(libs.testRunner)
+                implementation(libs.truth)
+
             }
         }
 
@@ -109,14 +119,27 @@
             dependsOn(jvmTest)
             dependsOn(androidCommonTest)
             dependencies {
+                implementation(libs.truth)
                 implementation(libs.robolectric)
                 implementation(libs.mockitoCore4)
                 implementation(libs.mockitoKotlin4)
+
+                implementation(project(":compose:animation:animation-core"))
+                implementation(project(":compose:material:material"))
+                implementation(project(":compose:test-utils"))
             }
         }
 
         desktopTest {
             dependsOn(jvmTest)
+            dependencies {
+                implementation(libs.truth)
+                implementation(libs.junit)
+                implementation(libs.kotlinTest)
+                implementation(libs.skikoCurrentOs)
+                implementation(project(":compose:foundation:foundation"))
+                implementation(project(":compose:ui:ui-test"))
+            }
         }
     }
 }
diff --git a/compose/ui/ui-test/lint-baseline.xml b/compose/ui/ui-test/lint-baseline.xml
index cfe69ca..ee8ffe4 100644
--- a/compose/ui/ui-test/lint-baseline.xml
+++ b/compose/ui/ui-test/lint-baseline.xml
@@ -11,6 +11,15 @@
     </issue>
 
     <issue
+        id="BanThreadSleep"
+        message="Uses Thread.sleep()"
+        errorLine1="                Thread.sleep(10)"
+        errorLine2="                       ~~~~~">
+        <location
+            file="src/androidMain/kotlin/androidx/compose/ui/test/ComposeUiTest.android.kt"/>
+    </issue>
+
+    <issue
         id="PrimitiveInCollection"
         message="field MouseAsTouchEvents with type List&lt;Integer>: replace with IntList"
         errorLine1="private val MouseAsTouchEvents = listOf(ACTION_DOWN, ACTION_MOVE, ACTION_UP)"
diff --git a/compose/ui/ui-test/src/androidInstrumentedTest/AndroidManifest.xml b/compose/ui/ui-test/src/androidInstrumentedTest/AndroidManifest.xml
index 63f7324..d70ca0d 100644
--- a/compose/ui/ui-test/src/androidInstrumentedTest/AndroidManifest.xml
+++ b/compose/ui/ui-test/src/androidInstrumentedTest/AndroidManifest.xml
@@ -19,5 +19,18 @@
         <activity android:name="androidx.compose.ui.test.ActivityWithActionBar" />
         <activity android:name="androidx.compose.ui.test.ClickCounterActivity" />
         <activity android:name="androidx.compose.ui.test.EmptyActivity" />
+
+        <activity android:name="androidx.compose.ui.test.junit4.CustomActivity"
+            android:theme="@android:style/Theme.Material.NoActionBar.Fullscreen" />
+        <activity android:name="androidx.compose.ui.test.junit4.MultipleActivitiesFindTest$Activity1" />
+        <activity android:name="androidx.compose.ui.test.junit4.MultipleActivitiesFindTest$Activity2" />
+        <activity android:name="androidx.compose.ui.test.junit4.MultipleActivitiesClickTest$Activity1" />
+        <activity android:name="androidx.compose.ui.test.junit4.MultipleActivitiesClickTest$Activity2" />
+        <activity android:name="androidx.compose.ui.test.junit4.MultipleActivitiesFirstDrawTest$Activity1" />
+        <activity android:name="androidx.compose.ui.test.junit4.MultipleActivitiesFirstDrawTest$Activity2" />
+        <activity android:name="androidx.compose.ui.test.junit4.LateSetContentTest$Activity" />
+        <activity android:name="androidx.compose.ui.test.junit4.MultipleActivitiesWithoutComposeTest$Activity1" />
+        <activity android:name="androidx.compose.ui.test.junit4.MultipleActivitiesWithoutComposeTest$Activity2" />
+        <activity android:name="androidx.compose.ui.test.junit4.MultipleActivitiesWithoutComposeTest$Activity3" />
     </application>
 </manifest>
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/ComposeUiTestTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/ComposeUiTestTest.kt
similarity index 98%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/ComposeUiTestTest.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/ComposeUiTestTest.kt
index 1195ad9..0071554 100644
--- a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/ComposeUiTestTest.kt
+++ b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/ComposeUiTestTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -38,7 +38,6 @@
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.ExperimentalComposeApi
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.getValue
@@ -66,7 +65,6 @@
 import kotlin.math.roundToInt
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Runnable
 import kotlinx.coroutines.test.StandardTestDispatcher
 import org.junit.After
@@ -80,7 +78,7 @@
 
 @LargeTest
 @RunWith(AndroidJUnit4::class)
-@OptIn(ExperimentalTestApi::class, ExperimentalComposeApi::class, ExperimentalCoroutinesApi::class)
+@OptIn(ExperimentalTestApi::class)
 class ComposeUiTestTest {
 
     private var idlingPolicy: IdlingPolicy? = null
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ApplySnapshotImmediatelyTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ApplySnapshotImmediatelyTest.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ApplySnapshotImmediatelyTest.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ApplySnapshotImmediatelyTest.kt
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeIdlingResourceTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeIdlingResourceTest.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeIdlingResourceTest.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeIdlingResourceTest.kt
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeInFragmentTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeInFragmentTest.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeInFragmentTest.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeInFragmentTest.kt
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeRootRegistryTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeRootRegistryTest.kt
similarity index 98%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeRootRegistryTest.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeRootRegistryTest.kt
index 9e8c31c..8add055 100644
--- a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeRootRegistryTest.kt
+++ b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeRootRegistryTest.kt
@@ -22,6 +22,7 @@
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
 import androidx.compose.ui.platform.ViewRootForTest
+import androidx.compose.ui.test.ComposeRootRegistry
 import androidx.test.ext.junit.rules.ActivityScenarioRule
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth.assertThat
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeTestRuleWaitUntilTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeTestRuleWaitUntilTest.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeTestRuleWaitUntilTest.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ComposeTestRuleWaitUntilTest.kt
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/CustomActivityTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/CustomActivityTest.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/CustomActivityTest.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/CustomActivityTest.kt
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/EspressoLinkTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/EspressoLinkTest.kt
similarity index 96%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/EspressoLinkTest.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/EspressoLinkTest.kt
index 44ea9a3..15fb33b 100644
--- a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/EspressoLinkTest.kt
+++ b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/EspressoLinkTest.kt
@@ -16,6 +16,8 @@
 
 package androidx.compose.ui.test.junit4
 
+import androidx.compose.ui.test.EspressoLink
+import androidx.compose.ui.test.IdlingResourceRegistry
 import androidx.compose.ui.test.InternalTestApi
 import androidx.test.espresso.Espresso
 import androidx.test.espresso.IdlingRegistry
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/FirstDrawTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/FirstDrawTest.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/FirstDrawTest.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/FirstDrawTest.kt
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/IdlingResourceRegistryTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/IdlingResourceRegistryTest.kt
similarity index 98%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/IdlingResourceRegistryTest.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/IdlingResourceRegistryTest.kt
index ea2a8e0..9d4133e 100644
--- a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/IdlingResourceRegistryTest.kt
+++ b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/IdlingResourceRegistryTest.kt
@@ -17,6 +17,7 @@
 package androidx.compose.ui.test.junit4
 
 import androidx.compose.ui.test.IdlingResource
+import androidx.compose.ui.test.IdlingResourceRegistry
 import androidx.compose.ui.test.InternalTestApi
 import androidx.test.annotation.UiThreadTest
 import androidx.test.ext.junit.runners.AndroidJUnit4
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/LateActivityLaunchTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/LateActivityLaunchTest.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/LateActivityLaunchTest.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/LateActivityLaunchTest.kt
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/LateSetContentTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/LateSetContentTest.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/LateSetContentTest.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/LateSetContentTest.kt
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/LaunchActivityTooEarlyTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/LaunchActivityTooEarlyTest.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/LaunchActivityTooEarlyTest.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/LaunchActivityTooEarlyTest.kt
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/MultipleActivitiesClickTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/MultipleActivitiesClickTest.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/MultipleActivitiesClickTest.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/MultipleActivitiesClickTest.kt
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/MultipleActivitiesFindTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/MultipleActivitiesFindTest.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/MultipleActivitiesFindTest.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/MultipleActivitiesFindTest.kt
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/MultipleActivitiesFirstDrawTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/MultipleActivitiesFirstDrawTest.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/MultipleActivitiesFirstDrawTest.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/MultipleActivitiesFirstDrawTest.kt
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/MultipleActivitiesWithoutComposeTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/MultipleActivitiesWithoutComposeTest.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/MultipleActivitiesWithoutComposeTest.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/MultipleActivitiesWithoutComposeTest.kt
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/MultipleComposeRootsTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/MultipleComposeRootsTest.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/MultipleComposeRootsTest.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/MultipleComposeRootsTest.kt
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/SynchronizationMethodsTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/SynchronizationMethodsTest.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/SynchronizationMethodsTest.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/SynchronizationMethodsTest.kt
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/TimeOutTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/TimeOutTest.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/TimeOutTest.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/TimeOutTest.kt
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/UncaughtExceptionsInCoroutinesTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/UncaughtExceptionsInCoroutinesTest.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/UncaughtExceptionsInCoroutinesTest.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/UncaughtExceptionsInCoroutinesTest.kt
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ViewVisibilityTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ViewVisibilityTest.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ViewVisibilityTest.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/ViewVisibilityTest.kt
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/WaitUntilNodeCountTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/WaitUntilNodeCountTest.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/WaitUntilNodeCountTest.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/WaitUntilNodeCountTest.kt
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/WaitingForOnCommitCallbackTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/WaitingForOnCommitCallbackTest.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/WaitingForOnCommitCallbackTest.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/WaitingForOnCommitCallbackTest.kt
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/WaitingForPopupTest.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/WaitingForPopupTest.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/WaitingForPopupTest.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/WaitingForPopupTest.kt
diff --git a/compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/util/BoundaryNodes.kt b/compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/util/BoundaryNodes.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/util/BoundaryNodes.kt
rename to compose/ui/ui-test/src/androidInstrumentedTest/kotlin/androidx/compose/ui/test/junit4/util/BoundaryNodes.kt
diff --git a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidSynchronization.android.kt b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidSynchronization.android.kt
similarity index 94%
rename from compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidSynchronization.android.kt
rename to compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidSynchronization.android.kt
index 80a758b..2981532 100644
--- a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/AndroidSynchronization.android.kt
+++ b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/AndroidSynchronization.android.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.test.junit4
+package androidx.compose.ui.test
 
 import android.annotation.SuppressLint
 import android.os.Looper
diff --git a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/ComposeIdlingResource.android.kt b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/ComposeIdlingResource.android.kt
similarity index 97%
rename from compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/ComposeIdlingResource.android.kt
rename to compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/ComposeIdlingResource.android.kt
index 26754c7..bab56602 100644
--- a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/ComposeIdlingResource.android.kt
+++ b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/ComposeIdlingResource.android.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,13 +14,12 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.test.junit4
+package androidx.compose.ui.test
 
 import android.view.View
 import androidx.compose.runtime.Recomposer
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.platform.ViewRootForTest
-import androidx.compose.ui.test.IdlingResource
 import kotlin.math.max
 
 /**
diff --git a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/ComposeRootRegistry.android.kt b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/ComposeRootRegistry.android.kt
similarity index 98%
rename from compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/ComposeRootRegistry.android.kt
rename to compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/ComposeRootRegistry.android.kt
index fd75534..cc24adc 100644
--- a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/ComposeRootRegistry.android.kt
+++ b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/ComposeRootRegistry.android.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.test.junit4
+package androidx.compose.ui.test
 
 import android.os.Handler
 import android.os.Looper
diff --git a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/ComposeUiTest.android.kt b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/ComposeUiTest.android.kt
similarity index 96%
rename from compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/ComposeUiTest.android.kt
rename to compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/ComposeUiTest.android.kt
index 33af8ea..e8fd13b 100644
--- a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/ComposeUiTest.android.kt
+++ b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/ComposeUiTest.android.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -28,17 +28,6 @@
 import androidx.compose.ui.node.RootForTest
 import androidx.compose.ui.platform.InfiniteAnimationPolicy
 import androidx.compose.ui.platform.WindowRecomposerPolicy
-import androidx.compose.ui.test.junit4.ComposeIdlingResource
-import androidx.compose.ui.test.junit4.ComposeRootRegistry
-import androidx.compose.ui.test.junit4.EspressoLink
-import androidx.compose.ui.test.junit4.IdlingResourceRegistry
-import androidx.compose.ui.test.junit4.IdlingStrategy
-import androidx.compose.ui.test.junit4.MainTestClockImpl
-import androidx.compose.ui.test.junit4.RobolectricIdlingStrategy
-import androidx.compose.ui.test.junit4.UncaughtExceptionHandler
-import androidx.compose.ui.test.junit4.awaitComposeRoots
-import androidx.compose.ui.test.junit4.isOnUiThread
-import androidx.compose.ui.test.junit4.waitForComposeRoots
 import androidx.compose.ui.unit.Density
 import androidx.test.core.app.ActivityScenario
 import androidx.test.core.app.ApplicationProvider
@@ -526,7 +515,7 @@
             get() = mainClockImpl
 
         override fun <T> runOnUiThread(action: () -> T): T {
-            return androidx.compose.ui.test.junit4.runOnUiThread(action)
+            return androidx.compose.ui.test.runOnUiThread(action)
         }
 
         override fun getRoots(atLeastOneRootExpected: Boolean): Set<RootForTest> {
diff --git a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/EspressoLink.android.kt b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/EspressoLink.android.kt
similarity index 98%
rename from compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/EspressoLink.android.kt
rename to compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/EspressoLink.android.kt
index fd9ab6a..0bd0a70 100644
--- a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/EspressoLink.android.kt
+++ b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/EspressoLink.android.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.test.junit4
+package androidx.compose.ui.test
 
 import androidx.compose.ui.test.junit4.android.ComposeNotIdleException
 import androidx.test.espresso.AppNotIdleException
diff --git a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/IdlingStrategy.android.kt b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/IdlingStrategy.android.kt
similarity index 95%
rename from compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/IdlingStrategy.android.kt
rename to compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/IdlingStrategy.android.kt
index d101330..385cdd4 100644
--- a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/IdlingStrategy.android.kt
+++ b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/IdlingStrategy.android.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2021 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.test.junit4
+package androidx.compose.ui.test
 
 /**
  * A strategy to wait for idleness. This is typically implemented by different test frameworks,
diff --git a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/MainTestClockImpl.android.kt b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/MainTestClockImpl.android.kt
similarity index 80%
rename from compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/MainTestClockImpl.android.kt
rename to compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/MainTestClockImpl.android.kt
index e535136..412a0cd 100644
--- a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/MainTestClockImpl.android.kt
+++ b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/MainTestClockImpl.android.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2021 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,8 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.test.junit4
+package androidx.compose.ui.test
 
-import androidx.compose.ui.test.ExperimentalTestApi
-import androidx.compose.ui.test.TestMonotonicFrameClock
-import androidx.compose.ui.test.frameDelayMillis
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestCoroutineScheduler
 
diff --git a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/RobolectricIdlingStrategy.android.kt b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/RobolectricIdlingStrategy.android.kt
similarity index 97%
rename from compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/RobolectricIdlingStrategy.android.kt
rename to compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/RobolectricIdlingStrategy.android.kt
index 5ff5012..adbe96e 100644
--- a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/RobolectricIdlingStrategy.android.kt
+++ b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/RobolectricIdlingStrategy.android.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2021 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.test.junit4
+package androidx.compose.ui.test
 
 import androidx.test.espresso.AppNotIdleException
 import androidx.test.espresso.IdlingPolicies
diff --git a/compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/android/ComposeNotIdleException.android.kt b/compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/junit4/android/ComposeNotIdleException.android.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/androidMain/kotlin/androidx/compose/ui/test/junit4/android/ComposeNotIdleException.android.kt
rename to compose/ui/ui-test/src/androidMain/kotlin/androidx/compose/ui/test/junit4/android/ComposeNotIdleException.android.kt
diff --git a/compose/ui/ui-test-junit4/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/InfiniteAnimationPolicyTest.kt b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/InfiniteAnimationPolicyTest.kt
similarity index 97%
rename from compose/ui/ui-test-junit4/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/InfiniteAnimationPolicyTest.kt
rename to compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/InfiniteAnimationPolicyTest.kt
index 310bfa7..ffa8528 100644
--- a/compose/ui/ui-test-junit4/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/InfiniteAnimationPolicyTest.kt
+++ b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/InfiniteAnimationPolicyTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2021 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/compose/ui/ui-test-junit4/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/RobolectricComposeTest.kt b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/RobolectricComposeTest.kt
similarity index 99%
rename from compose/ui/ui-test-junit4/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/RobolectricComposeTest.kt
rename to compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/RobolectricComposeTest.kt
index 15df0b3..6fbc231 100644
--- a/compose/ui/ui-test-junit4/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/RobolectricComposeTest.kt
+++ b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/RobolectricComposeTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2021 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/compose/ui/ui-test-junit4/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/ViewVisibilityRobolectricTest.kt b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/ViewVisibilityRobolectricTest.kt
similarity index 98%
rename from compose/ui/ui-test-junit4/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/ViewVisibilityRobolectricTest.kt
rename to compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/ViewVisibilityRobolectricTest.kt
index 15cf902..0ef6250 100644
--- a/compose/ui/ui-test-junit4/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/ViewVisibilityRobolectricTest.kt
+++ b/compose/ui/ui-test/src/androidUnitTest/kotlin/androidx/compose/ui/test/junit4/ViewVisibilityRobolectricTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/compose/ui/ui-test-junit4/src/commonMain/kotlin/androidx/compose/ui/test/ApplyingContinuationInterceptor.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/ApplyingContinuationInterceptor.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/commonMain/kotlin/androidx/compose/ui/test/ApplyingContinuationInterceptor.kt
rename to compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/ApplyingContinuationInterceptor.kt
diff --git a/compose/ui/ui-test-junit4/src/commonMain/kotlin/androidx/compose/ui/test/ComposeUiTest.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/ComposeUiTest.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/commonMain/kotlin/androidx/compose/ui/test/ComposeUiTest.kt
rename to compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/ComposeUiTest.kt
diff --git a/compose/ui/ui-test-junit4/src/commonMain/kotlin/androidx/compose/ui/test/StateRestorationTester.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/StateRestorationTester.kt
similarity index 100%
rename from compose/ui/ui-test-junit4/src/commonMain/kotlin/androidx/compose/ui/test/StateRestorationTester.kt
rename to compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/StateRestorationTester.kt
diff --git a/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/ComposeUiTest.desktop.kt b/compose/ui/ui-test/src/desktopMain/kotlin/androidx/compose/ui/test/ComposeUiTest.desktop.kt
similarity index 94%
rename from compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/ComposeUiTest.desktop.kt
rename to compose/ui/ui-test/src/desktopMain/kotlin/androidx/compose/ui/test/ComposeUiTest.desktop.kt
index 6977ddf..f619bc3 100644
--- a/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/ComposeUiTest.desktop.kt
+++ b/compose/ui/ui-test/src/desktopMain/kotlin/androidx/compose/ui/test/ComposeUiTest.desktop.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,12 +19,8 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.ComposeScene
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.node.RootForTest
 import androidx.compose.ui.platform.InfiniteAnimationPolicy
-import androidx.compose.ui.test.junit4.MainTestClockImpl
-import androidx.compose.ui.test.junit4.UncaughtExceptionHandler
-import androidx.compose.ui.test.junit4.isOnUiThread
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
 import kotlin.coroutines.CoroutineContext
@@ -49,7 +45,7 @@
  */
 @InternalTestApi
 @ExperimentalTestApi
-@OptIn(ExperimentalComposeUiApi::class, ExperimentalCoroutinesApi::class)
+@OptIn(ExperimentalCoroutinesApi::class)
 class DesktopComposeUiTest(
     effectContext: CoroutineContext = EmptyCoroutineContext
 ) : ComposeUiTest {
@@ -136,7 +132,7 @@
     }
 
     override fun <T> runOnUiThread(action: () -> T): T {
-        return androidx.compose.ui.test.junit4.runOnUiThread(action)
+        return androidx.compose.ui.test.runOnUiThread(action)
     }
 
     override fun <T> runOnIdle(action: () -> T): T {
diff --git a/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/DesktopSynchronization.desktop.kt b/compose/ui/ui-test/src/desktopMain/kotlin/androidx/compose/ui/test/DesktopSynchronization.desktop.kt
similarity index 93%
rename from compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/DesktopSynchronization.desktop.kt
rename to compose/ui/ui-test/src/desktopMain/kotlin/androidx/compose/ui/test/DesktopSynchronization.desktop.kt
index 9bc7aac..333d62d 100644
--- a/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/DesktopSynchronization.desktop.kt
+++ b/compose/ui/ui-test/src/desktopMain/kotlin/androidx/compose/ui/test/DesktopSynchronization.desktop.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.test.junit4
+package androidx.compose.ui.test
 
 import java.util.concurrent.ExecutionException
 import java.util.concurrent.FutureTask
diff --git a/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/MainTestClockImpl.desktop.kt b/compose/ui/ui-test/src/desktopMain/kotlin/androidx/compose/ui/test/MainTestClockImpl.desktop.kt
similarity index 81%
rename from compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/MainTestClockImpl.desktop.kt
rename to compose/ui/ui-test/src/desktopMain/kotlin/androidx/compose/ui/test/MainTestClockImpl.desktop.kt
index 5b21f1b..9f217ef 100644
--- a/compose/ui/ui-test-junit4/src/desktopMain/kotlin/androidx/compose/ui/test/junit4/MainTestClockImpl.desktop.kt
+++ b/compose/ui/ui-test/src/desktopMain/kotlin/androidx/compose/ui/test/MainTestClockImpl.desktop.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,12 +14,10 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.test.junit4
+package androidx.compose.ui.test
 
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestCoroutineScheduler
 
-@OptIn(ExperimentalCoroutinesApi::class)
 internal class MainTestClockImpl(
     testScheduler: TestCoroutineScheduler,
     frameDelayMillis: Long
diff --git a/compose/ui/ui-test-junit4/src/desktopTest/kotlin/androidx/compose/ui/test/ComposeUiTestTest.kt b/compose/ui/ui-test/src/desktopTest/kotlin/androidx/compose/ui/test/ComposeUiTestTest.kt
similarity index 69%
rename from compose/ui/ui-test-junit4/src/desktopTest/kotlin/androidx/compose/ui/test/ComposeUiTestTest.kt
rename to compose/ui/ui-test/src/desktopTest/kotlin/androidx/compose/ui/test/ComposeUiTestTest.kt
index 0dd6f32..09593b7 100644
--- a/compose/ui/ui-test-junit4/src/desktopTest/kotlin/androidx/compose/ui/test/ComposeUiTestTest.kt
+++ b/compose/ui/ui-test/src/desktopTest/kotlin/androidx/compose/ui/test/ComposeUiTestTest.kt
@@ -18,35 +18,17 @@
 
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.MotionDurationScale
-import androidx.compose.ui.test.junit4.createComposeRule
 import com.google.common.truth.Truth.assertThat
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
-import org.junit.Rule
 import org.junit.Test
-import org.junit.rules.TestWatcher
-import org.junit.runner.Description
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
-import org.junit.runners.model.Statement
 
 @RunWith(JUnit4::class)
 @OptIn(ExperimentalTestApi::class)
 class ComposeUiTestTest {
 
-    private lateinit var testDescription: Description
-
-    /**
-     * Records the current [testDescription] for tests that need to invoke the compose test rule
-     * directly.
-     */
-    @get:Rule
-    val testWatcher = object : TestWatcher() {
-        override fun starting(description: Description) {
-            testDescription = description
-        }
-    }
-
     @Test
     fun effectContextPropagatedToComposition_runComposeUiTest() {
         val testElement = TestCoroutineContextElement()
@@ -65,27 +47,6 @@
     }
 
     @Test
-    fun effectContextPropagatedToComposition_createComposeRule() {
-        val testElement = TestCoroutineContextElement()
-        lateinit var compositionScope: CoroutineScope
-        val rule = createComposeRule(testElement)
-        val baseStatement = object : Statement() {
-            override fun evaluate() {
-                rule.setContent {
-                    compositionScope = rememberCoroutineScope()
-                }
-                rule.waitForIdle()
-            }
-        }
-        rule.apply(baseStatement, testDescription)
-            .evaluate()
-
-        val elementFromComposition =
-            compositionScope.coroutineContext[TestCoroutineContextElement]
-        assertThat(elementFromComposition).isSameInstanceAs(testElement)
-    }
-
-    @Test
     fun motionDurationScale_defaultValue() = runComposeUiTest {
         var lastRecordedMotionDurationScale: Float? = null
         setContent {
diff --git a/compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/AbstractMainTestClock.jvm.kt b/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/AbstractMainTestClock.jvm.kt
similarity index 92%
rename from compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/AbstractMainTestClock.jvm.kt
rename to compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/AbstractMainTestClock.jvm.kt
index 3557dfa..6e4013c 100644
--- a/compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/AbstractMainTestClock.jvm.kt
+++ b/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/AbstractMainTestClock.jvm.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2021 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,10 +14,8 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.test.junit4
+package androidx.compose.ui.test
 
-import androidx.compose.ui.test.ComposeTimeoutException
-import androidx.compose.ui.test.MainTestClock
 import kotlin.math.ceil
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestCoroutineScheduler
diff --git a/compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/IdlingResourceRegistry.jvm.kt b/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/IdlingResourceRegistry.jvm.kt
similarity index 96%
rename from compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/IdlingResourceRegistry.jvm.kt
rename to compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/IdlingResourceRegistry.jvm.kt
index 1d7aba8..4cc556b 100644
--- a/compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/IdlingResourceRegistry.jvm.kt
+++ b/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/IdlingResourceRegistry.jvm.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,9 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.test.junit4
+package androidx.compose.ui.test
 
 import androidx.annotation.VisibleForTesting
-import androidx.compose.ui.test.IdlingResource
-import androidx.compose.ui.test.InternalTestApi
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
diff --git a/compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/UncaughtExceptionHandler.jvm.kt b/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/UncaughtExceptionHandler.jvm.kt
similarity index 96%
rename from compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/UncaughtExceptionHandler.jvm.kt
rename to compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/UncaughtExceptionHandler.jvm.kt
index 5518d55..7504672 100644
--- a/compose/ui/ui-test-junit4/src/jvmMain/kotlin/androidx/compose/ui/test/junit4/UncaughtExceptionHandler.jvm.kt
+++ b/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/UncaughtExceptionHandler.jvm.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2021 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.compose.ui.test.junit4
+package androidx.compose.ui.test
 
 import kotlin.coroutines.AbstractCoroutineContextElement
 import kotlin.coroutines.CoroutineContext
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt
index b4743ae..40bea10 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt
@@ -222,6 +222,11 @@
  * that is accessed by [block]:
  *
  * @sample androidx.compose.ui.samples.rememberedUpdatedParameterPointerInputModifier
+ *
+ * ***Note*** Any removal operations on Android Views from `pointerInput` should wrap the `block`
+ * in a `post { }` block to guarantee the event dispatch completes before executing the removal.
+ * (You do not need to do this when removing a composable because Compose guarantees it completes
+ * via the snapshot state system.)
  */
 fun Modifier.pointerInput(
     key1: Any?,
@@ -256,6 +261,11 @@
  * that is accessed by [block]:
  *
  * @sample androidx.compose.ui.samples.rememberedUpdatedParameterPointerInputModifier
+ *
+ * ***Note*** Any removal operations on Android Views from `pointerInput` should wrap the `block`
+ * in a `post { }` block to guarantee the event dispatch completes before executing the removal.
+ * (You do not need to do this when removing a composable because Compose guarantees it completes
+ * via the snapshot state system.)
  */
 fun Modifier.pointerInput(
     key1: Any?,
@@ -291,6 +301,11 @@
  * that is accessed by [block]:
  *
  * @sample androidx.compose.ui.samples.rememberedUpdatedParameterPointerInputModifier
+ *
+ * ***Note*** Any removal operations on Android Views from `pointerInput` should wrap the `block`
+ * in a `post { }` block to guarantee the event dispatch completes before executing the removal.
+ * (You do not need to do this when removing a composable because Compose guarantees it completes
+ * via the snapshot state system.)
  */
 fun Modifier.pointerInput(
     vararg keys: Any?,
diff --git a/core/core-appdigest/src/main/java/androidx/core/appdigest/ChecksumsApiSImpl.java b/core/core-appdigest/src/main/java/androidx/core/appdigest/ChecksumsApiSImpl.java
index d883448..6106c1d 100644
--- a/core/core-appdigest/src/main/java/androidx/core/appdigest/ChecksumsApiSImpl.java
+++ b/core/core-appdigest/src/main/java/androidx/core/appdigest/ChecksumsApiSImpl.java
@@ -103,6 +103,12 @@
                             + "list of certificates.");
         }
 
+        if (Build.VERSION.SDK_INT <= 34) {
+            // On certain U devices, this might throw NameNotFoundException even if package is
+            // present.
+            context.getPackageManager().getInstallSourceInfo(packageName);
+        }
+
         context.getPackageManager().requestChecksums(packageName, includeSplits, required,
                 trustedInstallers, new PackageManager.OnChecksumsReadyListener() {
                     @SuppressLint({"WrongConstant"})
diff --git a/core/core/src/androidTest/java/androidx/core/location/LocationCompatTest.java b/core/core/src/androidTest/java/androidx/core/location/LocationCompatTest.java
index 6cf5ee3..f26c738 100644
--- a/core/core/src/androidTest/java/androidx/core/location/LocationCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/location/LocationCompatTest.java
@@ -18,7 +18,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
@@ -105,8 +104,6 @@
         assertEquals(1.0, LocationCompat.getMslAltitudeMeters(location), 0.0);
         LocationCompat.removeMslAltitude(location);
         assertFalse(LocationCompat.hasMslAltitude(location));
-        assertThrows(IllegalStateException.class,
-                () -> LocationCompat.getMslAltitudeMeters(location));
     }
 
     @Test
@@ -118,8 +115,6 @@
         assertEquals(1f, LocationCompat.getMslAltitudeAccuracyMeters(location), 0f);
         LocationCompat.removeMslAltitudeAccuracy(location);
         assertFalse(LocationCompat.hasMslAltitudeAccuracy(location));
-        assertThrows(IllegalStateException.class,
-                () -> LocationCompat.getMslAltitudeAccuracyMeters(location));
     }
 
     @Test
diff --git a/core/core/src/main/java/androidx/core/location/LocationCompat.java b/core/core/src/main/java/androidx/core/location/LocationCompat.java
index 883ff8a..7d4c123 100644
--- a/core/core/src/main/java/androidx/core/location/LocationCompat.java
+++ b/core/core/src/main/java/androidx/core/location/LocationCompat.java
@@ -28,7 +28,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
-import androidx.core.util.Preconditions;
 
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
@@ -337,19 +336,18 @@
     /**
      * Returns the Mean Sea Level altitude of the location in meters.
      *
+     * <p>This is only valid if {@link #hasMslAltitude(Location)} is true.
+     *
      * <p>NOTE: On API levels below 34, the concept of Mean Sea Level altitude does not exist. In
      * order to allow for backwards compatibility and testing however, this method will attempt
      * to read a double extra with the key {@link #EXTRA_MSL_ALTITUDE} and return the result.
      *
-     * @throws IllegalStateException if the Mean Sea Level altitude of the location is not set
      * @see Location#getMslAltitudeMeters()
      */
     public static double getMslAltitudeMeters(@NonNull Location location) {
         if (VERSION.SDK_INT >= 34) {
             return Api34Impl.getMslAltitudeMeters(location);
         }
-        Preconditions.checkState(hasMslAltitude(location),
-                "The Mean Sea Level altitude of the location is not set.");
         return getOrCreateExtras(location).getDouble(EXTRA_MSL_ALTITUDE);
     }
 
@@ -411,22 +409,20 @@
      * altitude of the location falls within {@link #getMslAltitudeMeters(Location)} +/- this
      * uncertainty.
      *
+     * <p>This is only valid if {@link #hasMslAltitudeAccuracy(Location)} is true.
+     *
      * <p>NOTE: On API levels below 34, the concept of Mean Sea Level altitude accuracy does not
      * exist. In order to allow for backwards compatibility and testing however, this method will
      * attempt to read a float extra with the key {@link #EXTRA_MSL_ALTITUDE_ACCURACY} and return
      * the result.
      *
-     * @throws IllegalStateException if the Mean Sea Level altitude accuracy of the location is not
-     *                               set
-     * @see Location#setMslAltitudeAccuracyMeters(float)
+     * @see Location#getMslAltitudeAccuracyMeters()
      */
     public static @FloatRange(from = 0.0) float getMslAltitudeAccuracyMeters(
             @NonNull Location location) {
         if (VERSION.SDK_INT >= 34) {
             return Api34Impl.getMslAltitudeAccuracyMeters(location);
         }
-        Preconditions.checkState(hasMslAltitudeAccuracy(location),
-                "The Mean Sea Level altitude accuracy of the location is not set.");
         return getOrCreateExtras(location).getFloat(EXTRA_MSL_ALTITUDE_ACCURACY);
     }
 
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/impl/UwbManagerImpl.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/impl/UwbManagerImpl.kt
index 9345855..a8d9bd8 100644
--- a/core/uwb/uwb/src/main/java/androidx/core/uwb/impl/UwbManagerImpl.kt
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/impl/UwbManagerImpl.kt
@@ -30,6 +30,7 @@
 import androidx.core.uwb.UwbControllerSessionScope
 import androidx.core.uwb.UwbManager
 import androidx.core.uwb.backend.IUwb
+import androidx.core.uwb.exceptions.UwbServiceNotAvailableException
 import androidx.core.uwb.helper.checkSystemFeature
 import androidx.core.uwb.helper.handleApiException
 import com.google.android.gms.common.ConnectionResult
@@ -88,7 +89,8 @@
             Nearby.getUwbControllerClient(context) else Nearby.getUwbControleeClient(context)
         if (!uwbClient.isAvailable().await()) {
             Log.e(TAG, "Uwb availability : false")
-            throw RuntimeException("Cannot start a ranging session when UWB is unavailable")
+            throw UwbServiceNotAvailableException("Cannot start a ranging session when UWB is " +
+                "unavailable")
         }
         try {
             val nearbyLocalAddress = uwbClient.localAddress.await()
diff --git a/development/build_log_simplifier/build_log_simplifier.py b/development/build_log_simplifier/build_log_simplifier.py
index 34432aa..974acd6 100755
--- a/development/build_log_simplifier/build_log_simplifier.py
+++ b/development/build_log_simplifier/build_log_simplifier.py
@@ -275,6 +275,21 @@
 def remove_control_characters(line):
     return control_character_regex.sub("", line)
 
+# Removes strings from the input wherever they are found
+# This list is less convenient than the .ignore files:
+#   This list doesn't get autosuggested additions
+#   This list isn't automatically garbage collected
+#   Users interested in seeing the exemption history probably won't think to look here
+# This list does allow removing part of the text from a line and still validating the remainder of the line
+# If this list eventually gets long we might want to make it easier to update
+inline_ignores_regex = re.compile(
+    # b/300072778
+    "Sharing is only supported for boot loader classes because bootstrap classpath has been appended"
+)
+
+def remove_inline_ignores(line):
+    return re.sub(inline_ignores_regex, "", line)
+
 # Normalizes some filepaths to more easily simplify/skip some messages
 def normalize_paths(lines):
     # get OUT_DIR, DIST_DIR, and the path of the root of the checkout
@@ -490,6 +505,7 @@
     for log_path in log_paths:
         lines = readlines(log_path)
         lines = [remove_control_characters(line) for line in lines]
+        lines = [remove_inline_ignores(line) for line in lines]
         lines = normalize_paths(lines)
         all_lines += lines
     # load configuration
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index 689a78b..db23bb9 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -71,7 +71,7 @@
     samples("androidx.compose.foundation:foundation-layout-samples:1.6.0-beta02")
     samples("androidx.compose.foundation:foundation-samples:1.6.0-beta02")
     kmpDocs("androidx.compose.material3:material3:1.2.0-alpha12")
-    docs("androidx.compose.material3:material3-adaptive:1.0.0-alpha02")
+    kmpDocs("androidx.compose.material3:material3-adaptive:1.0.0-alpha02")
     samples("androidx.compose.material3:material3-adaptive-navigation-suite-samples:1.2.0-alpha12")
     samples("androidx.compose.material3:material3-adaptive-samples:1.2.0-alpha12")
     samples("androidx.compose.material3:material3-samples:1.2.0-alpha12")
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 53db4d4e..d408a73 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -224,6 +224,7 @@
 mockitoCore = { module = "org.mockito:mockito-core", version.ref = "mockito" }
 mockitoCore4 = { module = "org.mockito:mockito-core", version = "4.8.0" }
 mockitoAndroid = { module = "org.mockito:mockito-android", version.ref = "mockito" }
+mockitoAndroid5 = { module = "org.mockito:mockito-android", version = "5.8.0" }
 mockitoKotlin = { module = "org.mockito.kotlin:mockito-kotlin", version = "2.2.11" }
 mockitoKotlin4 = { module = "org.mockito.kotlin:mockito-kotlin", version = "4.0.0" }
 moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" }
diff --git a/graphics/graphics-core/api/1.0.0-beta01.txt b/graphics/graphics-core/api/1.0.0-beta01.txt
index bcf0955..7a22bee 100644
--- a/graphics/graphics-core/api/1.0.0-beta01.txt
+++ b/graphics/graphics-core/api/1.0.0-beta01.txt
@@ -8,7 +8,8 @@
     method public long getUsageFlags();
     method public boolean isClosed();
     method public androidx.graphics.CanvasBufferedRenderer.RenderRequest obtainRenderRequest();
-    method public void releaseBuffer(android.hardware.HardwareBuffer hardwareBuffer, androidx.hardware.SyncFenceCompat? fence);
+    method public void releaseBuffer(android.hardware.HardwareBuffer hardwareBuffer);
+    method public void releaseBuffer(android.hardware.HardwareBuffer hardwareBuffer, optional androidx.hardware.SyncFenceCompat? fence);
     method public void setContentRoot(android.graphics.RenderNode renderNode);
     method public void setLightSourceAlpha(float ambientShadowAlpha, float spotShadowAlpha);
     method public void setLightSourceGeometry(float lightX, float lightY, float lightZ, float lightRadius);
diff --git a/graphics/graphics-core/api/current.ignore b/graphics/graphics-core/api/current.ignore
index af77f0d..d2d6395 100644
--- a/graphics/graphics-core/api/current.ignore
+++ b/graphics/graphics-core/api/current.ignore
@@ -1,11 +1,3 @@
 // Baseline format: 1.0
-AddedMethod: androidx.graphics.CanvasBufferedRenderer.RenderRequest#draw(boolean, kotlin.coroutines.Continuation<? super androidx.graphics.CanvasBufferedRenderer.RenderResult>):
-    Added method androidx.graphics.CanvasBufferedRenderer.RenderRequest.draw(boolean,kotlin.coroutines.Continuation<? super androidx.graphics.CanvasBufferedRenderer.RenderResult>)
-AddedMethod: androidx.graphics.CanvasBufferedRenderer.RenderRequest#drawAsync(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.graphics.CanvasBufferedRenderer.RenderResult>):
-    Added method androidx.graphics.CanvasBufferedRenderer.RenderRequest.drawAsync(java.util.concurrent.Executor,androidx.core.util.Consumer<androidx.graphics.CanvasBufferedRenderer.RenderResult>)
-
-
-RemovedMethod: androidx.graphics.CanvasBufferedRenderer.RenderRequest#draw(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.graphics.CanvasBufferedRenderer.RenderResult>):
-    Removed method androidx.graphics.CanvasBufferedRenderer.RenderRequest.draw(java.util.concurrent.Executor,androidx.core.util.Consumer<androidx.graphics.CanvasBufferedRenderer.RenderResult>)
-RemovedMethod: androidx.graphics.CanvasBufferedRenderer.RenderRequest#drawSync(boolean):
-    Removed method androidx.graphics.CanvasBufferedRenderer.RenderRequest.drawSync(boolean)
+AddedMethod: androidx.graphics.CanvasBufferedRenderer#releaseBuffer(android.hardware.HardwareBuffer):
+    Added method androidx.graphics.CanvasBufferedRenderer.releaseBuffer(android.hardware.HardwareBuffer)
diff --git a/graphics/graphics-core/api/current.txt b/graphics/graphics-core/api/current.txt
index bcf0955..7a22bee 100644
--- a/graphics/graphics-core/api/current.txt
+++ b/graphics/graphics-core/api/current.txt
@@ -8,7 +8,8 @@
     method public long getUsageFlags();
     method public boolean isClosed();
     method public androidx.graphics.CanvasBufferedRenderer.RenderRequest obtainRenderRequest();
-    method public void releaseBuffer(android.hardware.HardwareBuffer hardwareBuffer, androidx.hardware.SyncFenceCompat? fence);
+    method public void releaseBuffer(android.hardware.HardwareBuffer hardwareBuffer);
+    method public void releaseBuffer(android.hardware.HardwareBuffer hardwareBuffer, optional androidx.hardware.SyncFenceCompat? fence);
     method public void setContentRoot(android.graphics.RenderNode renderNode);
     method public void setLightSourceAlpha(float ambientShadowAlpha, float spotShadowAlpha);
     method public void setLightSourceGeometry(float lightX, float lightY, float lightZ, float lightRadius);
diff --git a/graphics/graphics-core/api/restricted_1.0.0-beta01.txt b/graphics/graphics-core/api/restricted_1.0.0-beta01.txt
index 0c3a248..b421d28 100644
--- a/graphics/graphics-core/api/restricted_1.0.0-beta01.txt
+++ b/graphics/graphics-core/api/restricted_1.0.0-beta01.txt
@@ -8,7 +8,8 @@
     method public long getUsageFlags();
     method public boolean isClosed();
     method public androidx.graphics.CanvasBufferedRenderer.RenderRequest obtainRenderRequest();
-    method public void releaseBuffer(android.hardware.HardwareBuffer hardwareBuffer, androidx.hardware.SyncFenceCompat? fence);
+    method public void releaseBuffer(android.hardware.HardwareBuffer hardwareBuffer);
+    method public void releaseBuffer(android.hardware.HardwareBuffer hardwareBuffer, optional androidx.hardware.SyncFenceCompat? fence);
     method public void setContentRoot(android.graphics.RenderNode renderNode);
     method public void setLightSourceAlpha(float ambientShadowAlpha, float spotShadowAlpha);
     method public void setLightSourceGeometry(float lightX, float lightY, float lightZ, float lightRadius);
diff --git a/graphics/graphics-core/api/restricted_current.ignore b/graphics/graphics-core/api/restricted_current.ignore
index af77f0d..d2d6395 100644
--- a/graphics/graphics-core/api/restricted_current.ignore
+++ b/graphics/graphics-core/api/restricted_current.ignore
@@ -1,11 +1,3 @@
 // Baseline format: 1.0
-AddedMethod: androidx.graphics.CanvasBufferedRenderer.RenderRequest#draw(boolean, kotlin.coroutines.Continuation<? super androidx.graphics.CanvasBufferedRenderer.RenderResult>):
-    Added method androidx.graphics.CanvasBufferedRenderer.RenderRequest.draw(boolean,kotlin.coroutines.Continuation<? super androidx.graphics.CanvasBufferedRenderer.RenderResult>)
-AddedMethod: androidx.graphics.CanvasBufferedRenderer.RenderRequest#drawAsync(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.graphics.CanvasBufferedRenderer.RenderResult>):
-    Added method androidx.graphics.CanvasBufferedRenderer.RenderRequest.drawAsync(java.util.concurrent.Executor,androidx.core.util.Consumer<androidx.graphics.CanvasBufferedRenderer.RenderResult>)
-
-
-RemovedMethod: androidx.graphics.CanvasBufferedRenderer.RenderRequest#draw(java.util.concurrent.Executor, androidx.core.util.Consumer<androidx.graphics.CanvasBufferedRenderer.RenderResult>):
-    Removed method androidx.graphics.CanvasBufferedRenderer.RenderRequest.draw(java.util.concurrent.Executor,androidx.core.util.Consumer<androidx.graphics.CanvasBufferedRenderer.RenderResult>)
-RemovedMethod: androidx.graphics.CanvasBufferedRenderer.RenderRequest#drawSync(boolean):
-    Removed method androidx.graphics.CanvasBufferedRenderer.RenderRequest.drawSync(boolean)
+AddedMethod: androidx.graphics.CanvasBufferedRenderer#releaseBuffer(android.hardware.HardwareBuffer):
+    Added method androidx.graphics.CanvasBufferedRenderer.releaseBuffer(android.hardware.HardwareBuffer)
diff --git a/graphics/graphics-core/api/restricted_current.txt b/graphics/graphics-core/api/restricted_current.txt
index 0c3a248..b421d28 100644
--- a/graphics/graphics-core/api/restricted_current.txt
+++ b/graphics/graphics-core/api/restricted_current.txt
@@ -8,7 +8,8 @@
     method public long getUsageFlags();
     method public boolean isClosed();
     method public androidx.graphics.CanvasBufferedRenderer.RenderRequest obtainRenderRequest();
-    method public void releaseBuffer(android.hardware.HardwareBuffer hardwareBuffer, androidx.hardware.SyncFenceCompat? fence);
+    method public void releaseBuffer(android.hardware.HardwareBuffer hardwareBuffer);
+    method public void releaseBuffer(android.hardware.HardwareBuffer hardwareBuffer, optional androidx.hardware.SyncFenceCompat? fence);
     method public void setContentRoot(android.graphics.RenderNode renderNode);
     method public void setLightSourceAlpha(float ambientShadowAlpha, float spotShadowAlpha);
     method public void setLightSourceGeometry(float lightX, float lightY, float lightZ, float lightRadius);
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/CanvasBufferedRenderer.kt b/graphics/graphics-core/src/main/java/androidx/graphics/CanvasBufferedRenderer.kt
index 7b58718..13ac94f 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/CanvasBufferedRenderer.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/CanvasBufferedRenderer.kt
@@ -430,14 +430,28 @@
      * @param fence Optional [SyncFenceCompat] that should be waited upon before the buffer is
      * reused.
      */
-    fun releaseBuffer(hardwareBuffer: HardwareBuffer, fence: SyncFenceCompat?) {
+    @JvmOverloads
+    fun releaseBuffer(hardwareBuffer: HardwareBuffer, fence: SyncFenceCompat? = null) {
         mImpl.releaseBuffer(hardwareBuffer, fence)
     }
 
     /**
      * Class that contains data regarding the result of the render request. Consumers are to wait
-     * on the provided [SyncFenceCompat] before consuming the [HardwareBuffer] provided to as well
-     * as verify that the status returned by [RenderResult.status] returns [RenderResult.SUCCESS].
+     * on the provided [SyncFenceCompat] if it is non null before consuming the [HardwareBuffer]
+     * provided to as well as verify that the status returned by [RenderResult.status] returns
+     * [RenderResult.SUCCESS].
+     *
+     * For example:
+     * ```
+     *  fun handleRenderResult(result: RenderResult) {
+     *      // block on the fence if it is non-null
+     *      result.fence?.let { fence ->
+     *          fence.awaitForever()
+     *          fence.close()
+     *      }
+     *      // consume contents of RenderResult.hardwareBuffer
+     *  }
+     * ```
      */
     class RenderResult(
         private val buffer: HardwareBuffer,
@@ -448,13 +462,23 @@
         /**
          * [HardwareBuffer] that contains the result of the render request.
          * Consumers should be sure to block on the [SyncFenceCompat] instance
-         * provided in [fence] before consuming the contents of this buffer.
+         * provided in [fence] if it is non-null before consuming the contents of this buffer.
+         * If [fence] returns null then this [HardwareBuffer] can be consumed immediately.
          */
         val hardwareBuffer: HardwareBuffer
             get() = buffer
 
         /**
-         * Optional fence that should be waited upon before consuming [hardwareBuffer]
+         * Optional fence that should be waited upon before consuming [hardwareBuffer].
+         * On Android U and above, requests to render will return sooner and include this fence
+         * as a way to signal that the result of the render request is reflected in the contents
+         * of the buffer. This is done to reduce latency and provide opportunities for other systems
+         * to block on the fence on the behalf of the application.
+         * For example, [SurfaceControlCompat.Transaction.setBuffer] can be invoked with
+         * [RenderResult.hardwareBuffer] and [RenderResult.fence] respectively without the
+         * application having to explicitly block on this fence.
+         * For older Android versions, the rendering pipeline will automatically block on this fence
+         * and this value will return null.
          */
         val fence: SyncFenceCompat?
             get() = mFence
diff --git a/graphics/graphics-shapes/build.gradle b/graphics/graphics-shapes/build.gradle
index 45bc2b8..d7c1342 100644
--- a/graphics/graphics-shapes/build.gradle
+++ b/graphics/graphics-shapes/build.gradle
@@ -38,7 +38,7 @@
         commonMain {
             dependencies {
                 implementation(libs.kotlinStdlibCommon)
-                implementation project(':collection:collection')
+                implementation("androidx.collection:collection:1.4.0-beta02")
                 implementation("androidx.annotation:annotation:1.1.0")
             }
         }
diff --git a/health/health-services-client/api/current.txt b/health/health-services-client/api/current.txt
index b807507..b59d5a2 100644
--- a/health/health-services-client/api/current.txt
+++ b/health/health-services-client/api/current.txt
@@ -260,6 +260,7 @@
     field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Long>> STEPS_PER_MINUTE_STATS;
     field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> STEPS_TOTAL;
     field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> SWIMMING_LAP_COUNT;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> SWIMMING_LAP_COUNT_TOTAL;
     field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> SWIMMING_STROKES;
     field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> SWIMMING_STROKES_TOTAL;
     field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> VO2_MAX;
diff --git a/health/health-services-client/api/restricted_current.txt b/health/health-services-client/api/restricted_current.txt
index 53770c5..b777571 100644
--- a/health/health-services-client/api/restricted_current.txt
+++ b/health/health-services-client/api/restricted_current.txt
@@ -260,6 +260,7 @@
     field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Long>> STEPS_PER_MINUTE_STATS;
     field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> STEPS_TOTAL;
     field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> SWIMMING_LAP_COUNT;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> SWIMMING_LAP_COUNT_TOTAL;
     field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> SWIMMING_STROKES;
     field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> SWIMMING_STROKES_TOTAL;
     field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> VO2_MAX;
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/DataType.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataType.kt
index a3e87c7..7f01d87 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/data/DataType.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataType.kt
@@ -573,11 +573,16 @@
         val ACTIVE_EXERCISE_DURATION_TOTAL: AggregateDataType<Long, CumulativeDataPoint<Long>> =
             createCumulativeDataType("Active Exercise Duration")
 
-        /** Count of swimming laps since the start of the current active exercise. */
+        /** Count of swimming laps since the last update. */
         @JvmField
         val SWIMMING_LAP_COUNT: DeltaDataType<Long, IntervalDataPoint<Long>> =
             createIntervalDataType("Swim Lap Count")
 
+        /** Count of swimming laps since the start of the current active exercise. */
+        @JvmField
+        val SWIMMING_LAP_COUNT_TOTAL: AggregateDataType<Long, CumulativeDataPoint<Long>> =
+            createCumulativeDataType("Swim Lap Count")
+
         /** The number of repetitions of an exercise performed since the last update. */
         @JvmField
         val REP_COUNT: DeltaDataType<Long, IntervalDataPoint<Long>> =
@@ -685,6 +690,7 @@
             SPEED_STATS,
             STEPS_PER_MINUTE_STATS,
             STEPS_TOTAL,
+            SWIMMING_LAP_COUNT_TOTAL,
             SWIMMING_STROKES_TOTAL,
             VO2_MAX_STATS,
             WALKING_STEPS_TOTAL,
diff --git a/health/health-services-client/src/test/java/androidx/health/services/client/data/DataTypeTest.kt b/health/health-services-client/src/test/java/androidx/health/services/client/data/DataTypeTest.kt
index 6ab8729..7625478 100644
--- a/health/health-services-client/src/test/java/androidx/health/services/client/data/DataTypeTest.kt
+++ b/health/health-services-client/src/test/java/androidx/health/services/client/data/DataTypeTest.kt
@@ -28,7 +28,6 @@
 import androidx.health.services.client.data.DataType.Companion.LOCATION
 import androidx.health.services.client.data.DataType.Companion.STEPS
 import androidx.health.services.client.data.DataType.Companion.STEPS_DAILY
-import androidx.health.services.client.data.DataType.Companion.SWIMMING_LAP_COUNT
 import androidx.health.services.client.data.DataType.TimeType.Companion.INTERVAL
 import androidx.health.services.client.data.DataType.TimeType.Companion.UNKNOWN
 import androidx.health.services.client.proto.DataProto
@@ -199,8 +198,6 @@
         }.map { it.name }
         // Certain deltas are expected to not have aggregates
         val deltaNames = DataType.deltaDataTypes.toMutableSet().apply {
-            // Swimming lap count is already aggregated
-            remove(SWIMMING_LAP_COUNT)
             // Aggregate location doesn't make a lot of sense
             remove(LOCATION)
             // Dailies are used in passive and passive only deals with deltas
diff --git a/hilt/hilt-navigation-compose/api/current.txt b/hilt/hilt-navigation-compose/api/current.txt
index 99cdd72..b34c1d5 100644
--- a/hilt/hilt-navigation-compose/api/current.txt
+++ b/hilt/hilt-navigation-compose/api/current.txt
@@ -3,6 +3,7 @@
 
   public final class HiltViewModelKt {
     method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM hiltViewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String? key);
+    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel, reified VMF> VM hiltViewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String? key, kotlin.jvm.functions.Function1<? super VMF,? extends VM> creationCallback);
   }
 
 }
diff --git a/hilt/hilt-navigation-compose/api/restricted_current.txt b/hilt/hilt-navigation-compose/api/restricted_current.txt
index 40d2a82..8ff83a6 100644
--- a/hilt/hilt-navigation-compose/api/restricted_current.txt
+++ b/hilt/hilt-navigation-compose/api/restricted_current.txt
@@ -4,6 +4,7 @@
   public final class HiltViewModelKt {
     method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static androidx.lifecycle.ViewModelProvider.Factory? createHiltViewModelFactory(androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner);
     method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM hiltViewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String? key);
+    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel, reified VMF> VM hiltViewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String? key, kotlin.jvm.functions.Function1<? super VMF,? extends VM> creationCallback);
   }
 
 }
diff --git a/hilt/hilt-navigation-compose/src/androidTest/java/androidx/hilt/navigation/compose/HiltViewModelComposeTest.kt b/hilt/hilt-navigation-compose/src/androidTest/java/androidx/hilt/navigation/compose/HiltViewModelComposeTest.kt
index ad8f010..4b8cbb2 100644
--- a/hilt/hilt-navigation-compose/src/androidTest/java/androidx/hilt/navigation/compose/HiltViewModelComposeTest.kt
+++ b/hilt/hilt-navigation-compose/src/androidTest/java/androidx/hilt/navigation/compose/HiltViewModelComposeTest.kt
@@ -43,6 +43,9 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth.assertThat
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import dagger.hilt.android.AndroidEntryPoint
 import dagger.hilt.android.lifecycle.HiltViewModel
 import dagger.hilt.android.qualifiers.ApplicationContext
@@ -250,6 +253,27 @@
         assertThat(firstFactory).isNotSameInstanceAs(secondFactory)
     }
 
+    @Test
+    fun hiltViewModelAssisted() {
+        lateinit var viewModel: SimpleAssistedViewModel
+        composeTestRule.setContent {
+            val navController = rememberNavController()
+            NavHost(navController, startDestination = "Main") {
+                composable("Main") {
+                    viewModel = hiltViewModel<
+                            SimpleAssistedViewModel,
+                            SimpleAssistedViewModel.Factory> {
+                        it.create(42)
+                    }
+                }
+            }
+        }
+        composeTestRule.waitForIdle()
+
+        assertThat(viewModel).isNotNull()
+        assertThat(viewModel.i).isEqualTo(42)
+    }
+
     @Composable
     private fun NavigateButton(text: String, listener: () -> Unit = { }) {
         Button(onClick = listener) {
@@ -269,6 +293,21 @@
         @ApplicationContext val context: Context
     ) : ViewModel()
 
+    @HiltViewModel(assistedFactory = SimpleAssistedViewModel.Factory::class)
+    class SimpleAssistedViewModel @AssistedInject constructor(
+        val handle: SavedStateHandle,
+        val logger: MyLogger,
+        // TODO(kuanyingchou) Remove this after https://github.com/google/dagger/issues/3601 is
+        //  resolved.
+        @ApplicationContext val context: Context,
+        @Assisted val i: Int
+    ) : ViewModel() {
+        @AssistedFactory
+        interface Factory {
+            fun create(i: Int): SimpleAssistedViewModel
+        }
+    }
+
     class TestFragment(val composable: @Composable (Fragment) -> Unit) : Fragment() {
         override fun onCreateView(
             inflater: LayoutInflater,
diff --git a/hilt/hilt-navigation-compose/src/main/java/androidx/hilt/navigation/compose/HiltViewModel.kt b/hilt/hilt-navigation-compose/src/main/java/androidx/hilt/navigation/compose/HiltViewModel.kt
index 5619fe3..ea36c0d 100644
--- a/hilt/hilt-navigation-compose/src/main/java/androidx/hilt/navigation/compose/HiltViewModel.kt
+++ b/hilt/hilt-navigation-compose/src/main/java/androidx/hilt/navigation/compose/HiltViewModel.kt
@@ -23,8 +23,11 @@
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.ViewModelStoreOwner
+import androidx.lifecycle.viewmodel.CreationExtras
 import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
 import androidx.lifecycle.viewmodel.compose.viewModel
+import dagger.hilt.android.internal.lifecycle.HiltViewModelFactory
+import dagger.hilt.android.lifecycle.withCreationCallback
 
 /**
  * Returns an existing
@@ -49,6 +52,41 @@
     return viewModel(viewModelStoreOwner, key, factory = factory)
 }
 
+/**
+ * Returns an existing
+ * [HiltViewModel](https://dagger.dev/api/latest/dagger/hilt/android/lifecycle/HiltViewModel)
+ * -annotated [ViewModel]  with an [@AssistedInject]-annotated constructor or creates a new one scoped to the current navigation graph present on
+ * the {@link NavController} back stack.
+ *
+ * If no navigation graph is currently present then the current scope will be used, usually, a
+ * fragment or an activity.
+ *
+ * @sample androidx.hilt.navigation.compose.samples.NavComposable
+ * @sample androidx.hilt.navigation.compose.samples.NestedNavComposable
+ */
+@Composable
+inline fun <reified VM : ViewModel, reified VMF> hiltViewModel(
+    viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
+        "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
+    },
+    key: String? = null,
+    noinline creationCallback: (VMF) -> VM
+): VM {
+    val factory = createHiltViewModelFactory(viewModelStoreOwner)
+    return viewModel(
+        viewModelStoreOwner = viewModelStoreOwner,
+        key = key,
+        factory = factory,
+        extras = viewModelStoreOwner.run {
+            if (this is HasDefaultViewModelProviderFactory) {
+                this.defaultViewModelCreationExtras.withCreationCallback(creationCallback)
+            } else {
+                CreationExtras.Empty.withCreationCallback(creationCallback)
+            }
+        }
+    )
+}
+
 @Composable
 @PublishedApi
 internal fun createHiltViewModelFactory(
diff --git a/hilt/hilt-navigation-fragment/api/current.txt b/hilt/hilt-navigation-fragment/api/current.txt
index 7cbc428..abbed0e 100644
--- a/hilt/hilt-navigation-fragment/api/current.txt
+++ b/hilt/hilt-navigation-fragment/api/current.txt
@@ -3,6 +3,7 @@
 
   public final class HiltNavGraphViewModelLazyKt {
     method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> hiltNavGraphViewModels(androidx.fragment.app.Fragment, @IdRes int navGraphId);
+    method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel, reified VMF> kotlin.Lazy<VM> hiltNavGraphViewModels(androidx.fragment.app.Fragment, @IdRes int navGraphId, kotlin.jvm.functions.Function1<? super VMF,? extends VM> creationCallback);
   }
 
 }
diff --git a/hilt/hilt-navigation-fragment/api/restricted_current.txt b/hilt/hilt-navigation-fragment/api/restricted_current.txt
index 7cbc428..abbed0e 100644
--- a/hilt/hilt-navigation-fragment/api/restricted_current.txt
+++ b/hilt/hilt-navigation-fragment/api/restricted_current.txt
@@ -3,6 +3,7 @@
 
   public final class HiltNavGraphViewModelLazyKt {
     method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> hiltNavGraphViewModels(androidx.fragment.app.Fragment, @IdRes int navGraphId);
+    method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel, reified VMF> kotlin.Lazy<VM> hiltNavGraphViewModels(androidx.fragment.app.Fragment, @IdRes int navGraphId, kotlin.jvm.functions.Function1<? super VMF,? extends VM> creationCallback);
   }
 
 }
diff --git a/hilt/hilt-navigation-fragment/src/androidTest/java/androidx/hilt/navigation/fragment/HiltNavGraphViewModelLazyTest.kt b/hilt/hilt-navigation-fragment/src/androidTest/java/androidx/hilt/navigation/fragment/HiltNavGraphViewModelLazyTest.kt
index f454761..32dd2e6 100644
--- a/hilt/hilt-navigation-fragment/src/androidTest/java/androidx/hilt/navigation/fragment/HiltNavGraphViewModelLazyTest.kt
+++ b/hilt/hilt-navigation-fragment/src/androidTest/java/androidx/hilt/navigation/fragment/HiltNavGraphViewModelLazyTest.kt
@@ -33,6 +33,9 @@
 import androidx.test.filters.LargeTest
 import androidx.testutils.withActivity
 import com.google.common.truth.Truth.assertThat
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import dagger.hilt.android.AndroidEntryPoint
 import dagger.hilt.android.lifecycle.HiltViewModel
 import dagger.hilt.android.qualifiers.ApplicationContext
@@ -79,10 +82,15 @@
             val viewModel = withActivity { firstFragment.viewModel }
             val savedStateViewModel = withActivity { firstFragment.savedStateViewModel }
             val hiltSavedStateViewModel = withActivity { firstFragment.hiltSavedStateViewModel }
+            val hiltAssistedInjectViewModel = withActivity {
+                firstFragment.hiltAssistedInjectViewModel
+            }
             assertThat(viewModel).isNotNull()
             assertThat(savedStateViewModel).isNotNull()
             assertThat(hiltSavedStateViewModel).isNotNull()
             assertThat(hiltSavedStateViewModel.otherDep).isNotNull()
+            assertThat(hiltAssistedInjectViewModel).isNotNull()
+            assertThat(hiltAssistedInjectViewModel.id).isEqualTo("test")
 
             // First assert that the initial value is null. Note that we won't get the
             // default value from nav args passed to the destination as this viewmodel
@@ -112,6 +120,8 @@
                 .isSameInstanceAs(savedStateViewModel)
             assertThat(secondFragment.hiltSavedStateViewModel)
                 .isSameInstanceAs(hiltSavedStateViewModel)
+            assertThat(secondFragment.hiltAssistedInjectViewModel)
+                .isSameInstanceAs(hiltAssistedInjectViewModel)
             val savedValue: String? = secondFragment.savedStateViewModel
                 .savedStateHandle["test"]
             assertThat(savedValue).isEqualTo("test")
@@ -132,6 +142,8 @@
                 .isSameInstanceAs(savedStateViewModel)
             assertThat(recreatedFragment.hiltSavedStateViewModel)
                 .isSameInstanceAs(hiltSavedStateViewModel)
+            assertThat(recreatedFragment.hiltAssistedInjectViewModel)
+                .isSameInstanceAs(hiltAssistedInjectViewModel)
             val recreatedValue: String? = recreatedFragment.savedStateViewModel
                 .savedStateHandle["test"]
             assertThat(recreatedValue).isEqualTo("test")
@@ -148,6 +160,11 @@
     val savedStateViewModel: TestSavedStateViewModel by navGraphViewModels(R.id.vm_graph)
     val hiltSavedStateViewModel: TestHiltSavedStateViewModel
             by hiltNavGraphViewModels(R.id.vm_graph)
+    val hiltAssistedInjectViewModel: TestHiltAssistedInjectViewModel
+            by hiltNavGraphViewModels(R.id.vm_graph) {
+                    factory: TestHiltAssistedInjectViewModel.Factory ->
+                factory.create(id = "test")
+            }
     // TODO(kuanyingchou) Remove this after https://github.com/google/dagger/issues/3601 is resolved
     @Inject @ApplicationContext
     lateinit var applicationContext: Context
@@ -174,4 +191,16 @@
     val otherDep: OtherDep
 ) : ViewModel()
 
+@HiltViewModel(assistedFactory = TestHiltAssistedInjectViewModel.Factory::class)
+class TestHiltAssistedInjectViewModel @AssistedInject constructor(
+    val savedStateHandle: SavedStateHandle,
+    val otherDep: OtherDep,
+    @Assisted val id: String
+) : ViewModel() {
+    @AssistedFactory
+    interface Factory {
+        fun create(id: String): TestHiltAssistedInjectViewModel
+    }
+}
+
 class OtherDep @Inject constructor()
diff --git a/hilt/hilt-navigation-fragment/src/main/java/androidx/hilt/navigation/fragment/HiltNavGraphViewModelLazy.kt b/hilt/hilt-navigation-fragment/src/main/java/androidx/hilt/navigation/fragment/HiltNavGraphViewModelLazy.kt
index 099b97c..8d2702e 100644
--- a/hilt/hilt-navigation-fragment/src/main/java/androidx/hilt/navigation/fragment/HiltNavGraphViewModelLazy.kt
+++ b/hilt/hilt-navigation-fragment/src/main/java/androidx/hilt/navigation/fragment/HiltNavGraphViewModelLazy.kt
@@ -24,6 +24,7 @@
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelStore
 import androidx.navigation.fragment.findNavController
+import dagger.hilt.android.lifecycle.withCreationCallback
 
 /**
  * Returns a property delegate to access a
@@ -31,7 +32,7 @@
  * -annotated [ViewModel] scoped to a navigation graph present on the [NavController] back stack:
  * ```
  * class MyFragment : Fragment() {
- *     val viewmodel: MainViewModel by androidx.hilt.navigation.fragment.hiltNavGraphViewModels(R.navigation.main)
+ *     val viewmodel: MainViewModel by hiltNavGraphViewModels(R.navigation.main)
  * }
  * ```
  *
@@ -59,3 +60,46 @@
         extrasProducer = { backStackEntry.defaultViewModelCreationExtras }
     )
 }
+
+/**
+ * Returns a property delegate to access a
+ * [HiltViewModel](https://dagger.dev/api/latest/dagger/hilt/android/lifecycle/HiltViewModel)
+ * -annotated [ViewModel] with an [@AssistedInject]-annotated constructor that is scoped to a
+ * navigation graph present on the [NavController] back stack:
+ * ```
+ * class MyFragment : Fragment() {
+ *     val viewmodel: MainViewModel by hiltNavGraphViewModels(R.navigation.main) { factory: MainViewModelFactory ->
+ *         factory.create(...)
+ *     }
+ * }
+ * ```
+ *
+ * This property can be accessed only after this NavGraph is on the NavController back stack,
+ * and an attempt access prior to that will result in an IllegalArgumentException.
+ *
+ * @param navGraphId ID of a NavGraph that exists on the [NavController] back stack
+ * @param creationCallback callback that takes an @AssistedFactory-annotated factory and creates a HiltViewModel using @AssistedInject-annotated constructor.
+ */
+@MainThread
+@Suppress("MissingNullability") // Due to https://youtrack.jetbrains.com/issue/KT-39209
+public inline fun <reified VM : ViewModel, reified VMF : Any> Fragment.hiltNavGraphViewModels(
+    @IdRes navGraphId: Int,
+    noinline creationCallback: (VMF) -> VM
+): Lazy<VM> {
+    val backStackEntry by lazy {
+        findNavController().getBackStackEntry(navGraphId)
+    }
+    val storeProducer: () -> ViewModelStore = {
+        backStackEntry.viewModelStore
+    }
+    return createViewModelLazy(
+        viewModelClass = VM::class,
+        storeProducer = storeProducer,
+        factoryProducer = {
+            HiltViewModelFactory(requireActivity(), backStackEntry.defaultViewModelProviderFactory)
+        },
+        extrasProducer = {
+            backStackEntry.defaultViewModelCreationExtras.withCreationCallback(creationCallback)
+        }
+    )
+}
diff --git a/hilt/hilt-navigation/src/main/java/androidx/hilt/navigation/HiltNavBackStackEntry.kt b/hilt/hilt-navigation/src/main/java/androidx/hilt/navigation/HiltNavBackStackEntry.kt
index bfe393e..fc14f38 100644
--- a/hilt/hilt-navigation/src/main/java/androidx/hilt/navigation/HiltNavBackStackEntry.kt
+++ b/hilt/hilt-navigation/src/main/java/androidx/hilt/navigation/HiltNavBackStackEntry.kt
@@ -72,12 +72,8 @@
                 "but instead found: $ctx"
         )
     }
-    // TODO(kuanyingchou): The `owner` is actually not used. We pass
-    //  `activity` here since it's a NonNull parameter. This can be removed with Dagger 2.45.
     return HiltViewModelFactory.createInternal(
         /* activity = */ activity,
-        /* owner = */ activity,
-        /* defaultArgs = */ null,
         /* delegateFactory = */ delegateFactory
     )
 }
diff --git a/hilt/integration-tests/viewmodelapp/src/androidTest/java/androidx/hilt/integration/viewmodelapp/ActivityInjectionTest.kt b/hilt/integration-tests/viewmodelapp/src/androidTest/java/androidx/hilt/integration/viewmodelapp/ActivityInjectionTest.kt
index ba64f40..82d890d 100644
--- a/hilt/integration-tests/viewmodelapp/src/androidTest/java/androidx/hilt/integration/viewmodelapp/ActivityInjectionTest.kt
+++ b/hilt/integration-tests/viewmodelapp/src/androidTest/java/androidx/hilt/integration/viewmodelapp/ActivityInjectionTest.kt
@@ -25,6 +25,7 @@
 import androidx.test.filters.SdkSuppress
 import com.google.common.truth.Truth.assertThat
 import dagger.hilt.android.AndroidEntryPoint
+import dagger.hilt.android.lifecycle.withCreationCallback
 import dagger.hilt.android.testing.HiltAndroidRule
 import dagger.hilt.android.testing.HiltAndroidTest
 import org.junit.Rule
@@ -49,6 +50,8 @@
                 assertThat(activity.myViewModel).isNotNull()
                 assertThat(activity.myInjectedViewModel).isNotNull()
                 assertThat(activity.myNestedInjectedViewModel).isNotNull()
+                assertThat(activity.myAssistedInjectedViewModel).isNotNull()
+                assertThat(activity.myAssistedInjectedViewModel.bar).isEqualTo(42)
             }
         }
     }
@@ -59,5 +62,13 @@
         val myViewModel by viewModels<MyViewModel>()
         val myInjectedViewModel by viewModels<MyInjectedViewModel>()
         val myNestedInjectedViewModel by viewModels<TopClass.MyNestedInjectedViewModel>()
+        val myAssistedInjectedViewModel by viewModels<MyAssistedInjectedViewModel>(
+            extrasProducer = {
+                defaultViewModelCreationExtras.withCreationCallback<
+                        MyAssistedInjectedViewModel.Factory> { factory ->
+                    factory.create(42)
+                }
+            }
+        )
     }
 }
diff --git a/hilt/integration-tests/viewmodelapp/src/androidTest/java/androidx/hilt/integration/viewmodelapp/MyViewModels.kt b/hilt/integration-tests/viewmodelapp/src/androidTest/java/androidx/hilt/integration/viewmodelapp/MyViewModels.kt
index 040777a..2b40fe7 100644
--- a/hilt/integration-tests/viewmodelapp/src/androidTest/java/androidx/hilt/integration/viewmodelapp/MyViewModels.kt
+++ b/hilt/integration-tests/viewmodelapp/src/androidTest/java/androidx/hilt/integration/viewmodelapp/MyViewModels.kt
@@ -21,6 +21,9 @@
 import android.app.Application
 import androidx.lifecycle.AndroidViewModel
 import androidx.lifecycle.ViewModel
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import dagger.hilt.android.lifecycle.HiltViewModel
 import javax.inject.Inject
 
@@ -32,6 +35,18 @@
 @HiltViewModel
 class MyInjectedViewModel @Inject constructor(foo: Foo) : ViewModel()
 
+@Suppress("UNUSED_PARAMETER")
+@HiltViewModel(assistedFactory = MyAssistedInjectedViewModel.Factory::class)
+class MyAssistedInjectedViewModel @AssistedInject constructor(
+    foo: Foo,
+    @Assisted val bar: Int
+) : ViewModel() {
+    @AssistedFactory
+    interface Factory {
+        fun create(bar: Int): MyAssistedInjectedViewModel
+    }
+}
+
 object TopClass {
     @Suppress("UNUSED_PARAMETER")
     @HiltViewModel
diff --git a/libraryversions.toml b/libraryversions.toml
index d9efb69..17bcc57 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -15,16 +15,19 @@
 BROWSER = "1.8.0-beta01"
 BUILDSRC_TESTS = "1.0.0-alpha01"
 CAMERA = "1.4.0-alpha03"
+CAMERA_EFFECTS = "1.0.0-alpha01"
+CAMERA_MLKIT_VISION = "1.4.0-alpha03"
 CAMERA_PIPE = "1.0.0-alpha01"
 CAMERA_TESTING = "1.0.0-alpha01"
+CAMERA_VIEWFINDER = "1.4.0-alpha03"
 CARDVIEW = "1.1.0-alpha01"
 CAR_APP = "1.7.0-alpha01"
 COLLECTION = "1.4.0-beta02"
 COMPOSE = "1.7.0-alpha01"
 COMPOSE_COMPILER = "1.5.6"
 COMPOSE_MATERIAL3 = "1.2.0-beta01"
-COMPOSE_MATERIAL3_ADAPTIVE = "1.0.0-alpha02"
-COMPOSE_MATERIAL3_ADAPTIVE_NAVIGATION_SUITE = "1.0.0-alpha01"
+COMPOSE_MATERIAL3_ADAPTIVE = "1.0.0-alpha03"
+COMPOSE_MATERIAL3_ADAPTIVE_NAVIGATION_SUITE = "1.0.0-alpha02"
 COMPOSE_MATERIAL3_COMMON = "1.0.0-alpha01"
 COMPOSE_RUNTIME_TRACING = "1.0.0-beta01"
 CONSTRAINTLAYOUT = "2.2.0-alpha13"
@@ -96,7 +99,7 @@
 MEDIA = "1.7.0-rc01"
 MEDIA2 = "1.3.0-rc01"
 MEDIAROUTER = "1.7.0-beta01"
-METRICS = "1.0.0-alpha05"
+METRICS = "1.0.0-beta01"
 NAVIGATION = "2.8.0-alpha01"
 PAGING = "3.3.0-alpha03"
 PALETTE = "1.1.0-alpha01"
@@ -186,6 +189,9 @@
 BUILDSRC_TESTS_MAX_DEP_VERSIONS_DEP = { group = "androidx.buildSrc-tests-max-dep-versions-dep", atomicGroupVersion = "versions.BUILDSRC_TESTS", overrideInclude = [ ":buildSrc-tests:max-dep-versions:buildSrc-tests-max-dep-versions-dep" ] }
 BUILDSRC_TESTS_MAX_DEP_VERSIONS_MAIN = { group = "androidx.buildSrc-tests-max-dep-versions-main", atomicGroupVersion = "versions.BUILDSRC_TESTS", overrideInclude = [ ":buildSrc-tests:max-dep-versions:buildSrc-tests-max-dep-versions-main" ] }
 CAMERA = { group = "androidx.camera", atomicGroupVersion = "versions.CAMERA" }
+CAMERA_EFFECTS = { group = "androidx.camera", atomicGroupVersion = "versions.CAMERA_EFFECTS", overrideInclude = [ ":camera:camera-effects" ] }
+CAMERA_MLKIT_VISION = { group = "androidx.camera", atomicGroupVersion = "versions.CAMERA_MLKIT_VISION", overrideInclude = [ ":camera:camera-mlkit-vision" ] }
+CAMERA_VIEWFINDER = { group = "androidx.camera", atomicGroupVersion = "versions.CAMERA_VIEWFINDER", overrideInclude = [ ":camera:camera-viewfinder", ":camera:camera-viewfinder-compose", ":camera:camera-viewfinder-core" ] }
 CARDVIEW = { group = "androidx.cardview", atomicGroupVersion = "versions.CARDVIEW" }
 CAR_APP = { group = "androidx.car.app", atomicGroupVersion = "versions.CAR_APP" }
 COLLECTION = { group = "androidx.collection", atomicGroupVersion = "versions.COLLECTION" }
diff --git a/metrics/metrics-performance/api/1.0.0-beta01.txt b/metrics/metrics-performance/api/1.0.0-beta01.txt
new file mode 100644
index 0000000..319e59a
--- /dev/null
+++ b/metrics/metrics-performance/api/1.0.0-beta01.txt
@@ -0,0 +1,76 @@
+// Signature format: 4.0
+package androidx.metrics.performance {
+
+  public class FrameData {
+    ctor public FrameData(long frameStartNanos, long frameDurationUiNanos, boolean isJank, java.util.List<androidx.metrics.performance.StateInfo> states);
+    method public androidx.metrics.performance.FrameData copy();
+    method public final long getFrameDurationUiNanos();
+    method public final long getFrameStartNanos();
+    method public final java.util.List<androidx.metrics.performance.StateInfo> getStates();
+    method public final boolean isJank();
+    property public final long frameDurationUiNanos;
+    property public final long frameStartNanos;
+    property public final boolean isJank;
+    property public final java.util.List<androidx.metrics.performance.StateInfo> states;
+  }
+
+  public class FrameDataApi24 extends androidx.metrics.performance.FrameData {
+    ctor public FrameDataApi24(long frameStartNanos, long frameDurationUiNanos, long frameDurationCpuNanos, boolean isJank, java.util.List<androidx.metrics.performance.StateInfo> states);
+    method public final long getFrameDurationCpuNanos();
+    property public final long frameDurationCpuNanos;
+  }
+
+  public final class FrameDataApi31 extends androidx.metrics.performance.FrameDataApi24 {
+    ctor public FrameDataApi31(long frameStartNanos, long frameDurationUiNanos, long frameDurationCpuNanos, long frameDurationTotalNanos, long frameOverrunNanos, boolean isJank, java.util.List<androidx.metrics.performance.StateInfo> states);
+    method public long getFrameDurationTotalNanos();
+    method public long getFrameOverrunNanos();
+    property public final long frameDurationTotalNanos;
+    property public final long frameOverrunNanos;
+  }
+
+  public final class JankStats {
+    method @UiThread public static androidx.metrics.performance.JankStats createAndTrack(android.view.Window window, androidx.metrics.performance.JankStats.OnFrameListener frameListener);
+    method public float getJankHeuristicMultiplier();
+    method public boolean isTrackingEnabled();
+    method public void setJankHeuristicMultiplier(float);
+    method @UiThread public void setTrackingEnabled(boolean);
+    property public final boolean isTrackingEnabled;
+    property public final float jankHeuristicMultiplier;
+    field public static final androidx.metrics.performance.JankStats.Companion Companion;
+  }
+
+  public static final class JankStats.Companion {
+    method @UiThread public androidx.metrics.performance.JankStats createAndTrack(android.view.Window window, androidx.metrics.performance.JankStats.OnFrameListener frameListener);
+  }
+
+  public static fun interface JankStats.OnFrameListener {
+    method public void onFrame(androidx.metrics.performance.FrameData volatileFrameData);
+  }
+
+  public final class PerformanceMetricsState {
+    method @UiThread public static androidx.metrics.performance.PerformanceMetricsState.Holder getHolderForHierarchy(android.view.View view);
+    method public void putSingleFrameState(String key, String value);
+    method public void putState(String key, String value);
+    method public void removeState(String key);
+    field public static final androidx.metrics.performance.PerformanceMetricsState.Companion Companion;
+  }
+
+  public static final class PerformanceMetricsState.Companion {
+    method @UiThread public androidx.metrics.performance.PerformanceMetricsState.Holder getHolderForHierarchy(android.view.View view);
+  }
+
+  public static final class PerformanceMetricsState.Holder {
+    method public androidx.metrics.performance.PerformanceMetricsState? getState();
+    property public final androidx.metrics.performance.PerformanceMetricsState? state;
+  }
+
+  public final class StateInfo {
+    ctor public StateInfo(String key, String value);
+    method public String getKey();
+    method public String getValue();
+    property public final String key;
+    property public final String value;
+  }
+
+}
+
diff --git a/metrics/metrics-performance/api/res-1.0.0-beta01.txt b/metrics/metrics-performance/api/res-1.0.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/metrics/metrics-performance/api/res-1.0.0-beta01.txt
diff --git a/metrics/metrics-performance/api/restricted_1.0.0-beta01.txt b/metrics/metrics-performance/api/restricted_1.0.0-beta01.txt
new file mode 100644
index 0000000..319e59a
--- /dev/null
+++ b/metrics/metrics-performance/api/restricted_1.0.0-beta01.txt
@@ -0,0 +1,76 @@
+// Signature format: 4.0
+package androidx.metrics.performance {
+
+  public class FrameData {
+    ctor public FrameData(long frameStartNanos, long frameDurationUiNanos, boolean isJank, java.util.List<androidx.metrics.performance.StateInfo> states);
+    method public androidx.metrics.performance.FrameData copy();
+    method public final long getFrameDurationUiNanos();
+    method public final long getFrameStartNanos();
+    method public final java.util.List<androidx.metrics.performance.StateInfo> getStates();
+    method public final boolean isJank();
+    property public final long frameDurationUiNanos;
+    property public final long frameStartNanos;
+    property public final boolean isJank;
+    property public final java.util.List<androidx.metrics.performance.StateInfo> states;
+  }
+
+  public class FrameDataApi24 extends androidx.metrics.performance.FrameData {
+    ctor public FrameDataApi24(long frameStartNanos, long frameDurationUiNanos, long frameDurationCpuNanos, boolean isJank, java.util.List<androidx.metrics.performance.StateInfo> states);
+    method public final long getFrameDurationCpuNanos();
+    property public final long frameDurationCpuNanos;
+  }
+
+  public final class FrameDataApi31 extends androidx.metrics.performance.FrameDataApi24 {
+    ctor public FrameDataApi31(long frameStartNanos, long frameDurationUiNanos, long frameDurationCpuNanos, long frameDurationTotalNanos, long frameOverrunNanos, boolean isJank, java.util.List<androidx.metrics.performance.StateInfo> states);
+    method public long getFrameDurationTotalNanos();
+    method public long getFrameOverrunNanos();
+    property public final long frameDurationTotalNanos;
+    property public final long frameOverrunNanos;
+  }
+
+  public final class JankStats {
+    method @UiThread public static androidx.metrics.performance.JankStats createAndTrack(android.view.Window window, androidx.metrics.performance.JankStats.OnFrameListener frameListener);
+    method public float getJankHeuristicMultiplier();
+    method public boolean isTrackingEnabled();
+    method public void setJankHeuristicMultiplier(float);
+    method @UiThread public void setTrackingEnabled(boolean);
+    property public final boolean isTrackingEnabled;
+    property public final float jankHeuristicMultiplier;
+    field public static final androidx.metrics.performance.JankStats.Companion Companion;
+  }
+
+  public static final class JankStats.Companion {
+    method @UiThread public androidx.metrics.performance.JankStats createAndTrack(android.view.Window window, androidx.metrics.performance.JankStats.OnFrameListener frameListener);
+  }
+
+  public static fun interface JankStats.OnFrameListener {
+    method public void onFrame(androidx.metrics.performance.FrameData volatileFrameData);
+  }
+
+  public final class PerformanceMetricsState {
+    method @UiThread public static androidx.metrics.performance.PerformanceMetricsState.Holder getHolderForHierarchy(android.view.View view);
+    method public void putSingleFrameState(String key, String value);
+    method public void putState(String key, String value);
+    method public void removeState(String key);
+    field public static final androidx.metrics.performance.PerformanceMetricsState.Companion Companion;
+  }
+
+  public static final class PerformanceMetricsState.Companion {
+    method @UiThread public androidx.metrics.performance.PerformanceMetricsState.Holder getHolderForHierarchy(android.view.View view);
+  }
+
+  public static final class PerformanceMetricsState.Holder {
+    method public androidx.metrics.performance.PerformanceMetricsState? getState();
+    property public final androidx.metrics.performance.PerformanceMetricsState? state;
+  }
+
+  public final class StateInfo {
+    ctor public StateInfo(String key, String value);
+    method public String getKey();
+    method public String getValue();
+    property public final String key;
+    property public final String value;
+  }
+
+}
+
diff --git a/metrics/metrics-performance/src/androidTest/java/androidx/metrics/performance/test/JankStatsTest.kt b/metrics/metrics-performance/src/androidTest/java/androidx/metrics/performance/test/JankStatsTest.kt
index 18928bb..1e9c73d 100644
--- a/metrics/metrics-performance/src/androidTest/java/androidx/metrics/performance/test/JankStatsTest.kt
+++ b/metrics/metrics-performance/src/androidTest/java/androidx/metrics/performance/test/JankStatsTest.kt
@@ -16,7 +16,6 @@
 package androidx.metrics.performance.test
 
 import android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1
-import android.util.Log
 import android.view.Choreographer
 import androidx.metrics.performance.FrameData
 import androidx.metrics.performance.FrameDataApi24
@@ -188,9 +187,13 @@
         initFramePipeline()
 
         var numSecondListenerCalls = 0
-        val secondListenerStates = mutableListOf<StateInfo>()
+        val secondListenerFrameData = mutableListOf<FrameData>()
         val secondListener = OnFrameListener { volatileFrameData ->
-            secondListenerStates.addAll(volatileFrameData.states)
+            // Sometimes we get a late frame arrival while we are checking the data.
+            // This sync call prevents ConcurrentModException
+            synchronized(secondListenerFrameData) {
+                secondListenerFrameData.add(volatileFrameData.copy())
+            }
             numSecondListenerCalls++
             if (numSecondListenerCalls >= NUM_FRAMES) {
                 secondListenerLatch.countDown()
@@ -201,19 +204,30 @@
         scenario.onActivity { _ ->
             jankStats2 = JankStats.createAndTrack(delayedActivity.window, secondListener)
         }
-        val testState = StateInfo("Testing State", "sampleState")
-        metricsState.putSingleFrameState(testState.key, testState.value)
 
+        resetFrameStates()
+        val testState = StateInfo("Testing State", "sampleState")
+        val insertTime = System.nanoTime()
+        metricsState.putSingleFrameState(testState.key, testState.value)
         // in case earlier frames arrive before our test begins
-        secondListenerStates.clear()
+        secondListenerFrameData.clear()
         secondListenerLatch = CountDownLatch(1)
-        latchedListener.reset()
+
         runDelayTest(frameDelay, NUM_FRAMES, latchedListener)
         secondListenerLatch.await(frameDelay * NUM_FRAMES + 1000L, TimeUnit.MILLISECONDS)
-        val jankData: FrameData = latchedListener.jankData[0]
         assertTrue("No calls to second listener", numSecondListenerCalls > 0)
-        assertEquals(listOf(testState), jankData.states)
-        assertEquals(listOf(testState), secondListenerStates)
+
+        // Test in both jankData.states and secondListenerStates:
+        // - Ensure that testState exists in the list of states
+        // - Ensure that frameStart +  for that frameData is greater than insertTime
+        // - Ensure that that state exists only once in the list
+
+        assertEquals("Should be exactly one occurrence of SingleFrameState",
+            1, checkSingleStateExistence(testState, latchedListener.jankData, insertTime))
+        synchronized(secondListenerFrameData) {
+            assertEquals("Should be exactly one occurrence of SingleFrameState", 1,
+                checkSingleStateExistence(testState, secondListenerFrameData, insertTime))
+        }
 
         jankStats2.isTrackingEnabled = false
         numSecondListenerCalls = 0
@@ -246,7 +260,31 @@
         listenerPostingThread.start()
         // add listeners concurrently - no asserts here, just testing whether we
         // avoid any concurrency issues with adding and using multiple listeners
-        runDelayTest(frameDelay, NUM_FRAMES * 100, latchedListener)
+        runDelayTest(frameDelay, NUM_FRAMES, latchedListener)
+    }
+
+    /**
+     * Ensure that there is only one occurrence of a given StateInfo entry. This is used to
+     * validate that SingleFrameState does the right thing - inserts into the current frame and
+     * removes it immediately.
+     */
+    fun checkSingleStateExistence(
+        singleState: StateInfo,
+        frameData: List<FrameData>,
+        insertionTimeNanos: Long
+    ): Int {
+        var numOccurrences = 0
+        for (item in frameData) {
+            for (state in item.states) {
+                if (state.equals(singleState)) {
+                    numOccurrences++
+                    assertTrue("State be added before frame end time",
+                        (item.frameStartNanos + item.frameDurationUiNanos) >
+                            insertionTimeNanos)
+                }
+            }
+        }
+        return numOccurrences
     }
 
     @Test
@@ -279,7 +317,7 @@
 
         initFramePipeline()
 
-        resetFrameStateData()
+        resetFrameStates()
 
         val state0 = StateInfo("Testing State 0", "sampleStateA")
         val state1 = StateInfo("Testing State 1", "sampleStateB")
@@ -318,12 +356,12 @@
         }
 
         // reset and clear states
-        resetFrameStateData()
+        resetFrameStates()
         latchedListener.reset()
         metricsState.removeState(state0.key)
         metricsState.removeState(state1.key)
 
-        runDelayTest(frameDelay, 1, latchedListener)
+        syncFrameStates()
         item0 = latchedListener.jankData[0]
         assertEquals(
             "States should be empty after being cleared, but got ${item0.states}",
@@ -335,16 +373,16 @@
         val state4 = Pair("Testing State 4", "sampleStateE")
         metricsState.putState(state3.first, state3.second)
         metricsState.putState(state4.first, state4.second)
-        runDelayTest(frameDelay, 1, latchedListener)
+        syncFrameStates()
         item0 = latchedListener.jankData[0]
         assertEquals("states: ${item0.states}", 2, item0.states.size)
         latchedListener.reset()
 
         // Test removal of state3 and replacement of state4
-        resetFrameStateData()
+        resetFrameStates()
         metricsState.removeState(state3.first)
         metricsState.putState(state4.first, "sampleStateF")
-        runDelayTest(frameDelay, 1, latchedListener)
+        syncFrameStates()
         item0 = latchedListener.jankData[0]
         assertEquals("states: ${item0.states}", 1, item0.states.size)
         assertEquals(state4.first, item0.states[0].key)
@@ -459,8 +497,23 @@
             JankStatsTest.FrameStateInputData(
                 addStates = listOf("stateNameA" to "0", "stateNameA" to "1"),
             ),
+            // 12-16: empty, just to allow extra frames to pulse
+            // Run more than the exact number of frames we have states for. Sometimes the system
+            // isn't done running all of the frames in which these states should go by the
+            // time we've run that number of frames.
+            JankStatsTest.FrameStateInputData(),
+            JankStatsTest.FrameStateInputData(),
+            JankStatsTest.FrameStateInputData(),
+            JankStatsTest.FrameStateInputData(),
+            JankStatsTest.FrameStateInputData(),
         )
-        // testData will hold input (above) plus expected results
+        // expectedResults holds the values of the states which we would expect to see in
+        // a normal test run.
+        // This list is currently unused due to flaky test issues related to race conditions
+        // between the test/UI thread and the FrameMetrics thread. It's difficult to
+        // deterministically insert and then test against data landing in specific frames.
+        // Leaving this here for future reference if we want to make the tests more robust
+        // eventually.
         val expectedResults = listOf(
             mapOf("stateNameA" to "0"),
             mapOf("stateNameA" to "0"),
@@ -476,39 +529,69 @@
             mapOf("stateNameA" to "1"),
         )
 
-        resetFrameStateData()
+        resetFrameStates()
         runDelayTest(frameDelay = 0, numFrames = perFrameStateData.size,
             latchedListener, perFrameStateData)
 
         // There might be one or two dropped frames, check that we have nearly the number
         // expected
-        assertTrue("There should be at least ${expectedResults.size - 2} frames of data",
+        assertTrue("There should be at least ${expectedResults.size - 2} frames of data" +
+            "but there were ${latchedListener.jankData.size}",
             (latchedListener.jankData.size > expectedResults.size - 2))
 
-        /*
-        Ideally, we would check each frame's result states against the expected results.
-        But the system sometimes drops frames, causing the jankData to be a subset of
-        the expectedResults set from above. This is fine, for testing purposes, but that
-        means we should check the current result against the expected result of this and
-        the next frame, to account for these skips. when this happens, we increment the
-        expected index since all results will be offset by that skip.
-         */
-        var expectedIndex = 0
-        var resultIndex = 0
-        while (expectedIndex < expectedResults.size &&
-            resultIndex < latchedListener.jankData.size) {
-            val testResultStates = latchedListener.jankData[resultIndex].states
-            // Test against this and next expected result, in case system skipped a frame
-            var matched = checkFrameStates(expectedResults[expectedIndex], testResultStates)
-            if (!matched) {
-                expectedIndex++
-                matched = checkFrameStates(expectedResults[expectedIndex], testResultStates)
+        // A more flexible way to check for the above, accounting for very minor frame boundary
+        // collisions which could cause states to be off by a frame or so, is to check
+        // the sequence of values that any state goes through in the results:
+        // stateNameA: 0, 1, 2, none, 0, none, 1
+        // stateNameB: none, 10, none
+        // stateNameC: none, 100, none
+        // Even this runs into problems, however, so disabling checks in this test for now.
+
+//        checkComplexFrameStates("stateNameA",
+//            arrayOf("0", "1", "2", null, "0", null, "1"))
+//        checkComplexFrameStates("stateNameB", arrayOf<String?>(null, "10", null))
+//        checkComplexFrameStates("stateNameC", arrayOf<String?>(null, "100", null))
+    }
+
+    /**
+     * Currently unused - this function checks the given frame data against a set of known
+     * states, in order. This works in general, but occasionally one of the states is wrong
+     * (due to multi-threaded race conditions with the frameMetrics thread, I suspect), so
+     * not used for now. Leaving it here in case we want more robust testing in the future.
+     */
+    private fun checkComplexFrameStates(stateName: String, stateValues: Array<String?>) {
+        var stateValuesIndex = 0
+        var currStateValue: String? = "placeholder"
+        // Iterating on the frame data has potential ConcurrentModificationException issues since
+        // the thread placing data in that array is running asynchronously. It should be done
+        // by the time we check the data, but may still be running anyway
+        for (frameData in latchedListener.jankData) {
+            val nextStateValue = stateValues[stateValuesIndex]
+            var matched = false
+            for (state in frameData.states) {
+                if (state.key == stateName) {
+                    if (nextStateValue == state.value) {
+                        matched = true
+                        ++stateValuesIndex
+                        currStateValue = state.value
+                        break
+                    } else {
+                        assertEquals("Next state value not correct",
+                            currStateValue, state.value)
+                        matched = true
+                    }
+                }
             }
-            assertTrue("Expected states do not match $testResultStates at frame " +
-                "$expectedIndex", matched)
-            expectedIndex++
-            resultIndex++
+            if (!matched) {
+                if (currStateValue != null) {
+                    assertEquals(nextStateValue, null)
+                    currStateValue = null
+                    ++stateValuesIndex
+                }
+            }
+            if (stateValuesIndex >= stateValues.size) break
         }
+        assertEquals(stateValuesIndex, stateValues.size)
     }
 
     private fun checkFrameStates(
@@ -530,32 +613,48 @@
      * pre-date the current time, which is when we might be setting/removing state.
      *
      * To ensure that the right thing happens, call this function prior to setting any frame state.
-     * It will run frames through the system until the frameData start timeis after the
-     * current time when this function is called.
+     * It will run frames through the system until the frameData start time is after the
+     * current time when this function is called. Then it will reset [latchedListener] to clear
+     * it of any state data just processed.
      */
-    private fun resetFrameStateData() {
-        val currentNanos = System.nanoTime()
-        // failsafe - limit the iterations, don't want to loop forever
-        var numAttempts = 0
+    private fun resetFrameStates() {
         try {
-            while (numAttempts < 100) {
-                runDelayTest(0, 1, latchedListener)
-                if (latchedListener.jankData.size > 0) {
-                    if (latchedListener.jankData[0].frameStartNanos > currentNanos) {
-                        return
-                    }
-                }
-                Log.d("JankStatsTest", "resetFrameStateData attempt $numAttempts:" +
-                    "frame start < currentTime: " +
-                    "${latchedListener.jankData[0].frameStartNanos}, $currentNanos")
-                latchedListener.reset()
-                numAttempts++
-            }
+            syncFrameStates()
         } finally {
             latchedListener.reset()
         }
     }
 
+    /**
+     * When we add or remove a frame state, it records the time for that request, then waits for
+     * a later frame starting after that time to actually add/remove those states. This sometimes
+     * breaks when there is an existing frame still to be processed and we only wait for that
+     * single frame to pulse. Because the frame started before the state request(s), they are not
+     * added/removed as expected and tests can fail.
+     *
+     * The solution is to pulse frames until we see a frame happen after the current time, which
+     * should be sufficient (since this function should only be called after the state requests
+     * have been made, thus before the current time, thus before the frame we are waiting for).
+     * This function does just that; pulses frames until one has a start time after the time
+     * when this function is called. Then we record the state settings for that frame and return.
+     */
+    private fun syncFrameStates() {
+        val currentNanos = System.nanoTime()
+        // failsafe - limit the iterations, don't want to loop forever. Typically we will
+        // only run for one or two frames.
+        var numAttempts = 0
+        while (numAttempts < 100) {
+            runDelayTest(0, 1, latchedListener)
+            if (latchedListener.jankData.size > 0) {
+                if (latchedListener.jankData[0].frameStartNanos > currentNanos) {
+                    return
+                }
+            }
+            latchedListener.reset()
+            numAttempts++
+        }
+    }
+
     private fun runDelayTest(
         frameDelay: Int,
         numFrames: Int,
diff --git a/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/NavControllerWithFragmentTest.kt b/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/NavControllerWithFragmentTest.kt
index 60cfb2c..bb70163 100644
--- a/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/NavControllerWithFragmentTest.kt
+++ b/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/NavControllerWithFragmentTest.kt
@@ -338,6 +338,51 @@
         )
     }
 
+    @Test
+    fun testPopToInitialInFragmentStarted() = withNavigationActivity {
+        navController.graph = navController.createGraph("first") {
+            fragment<EmptyFragment>("first")
+            fragment<PopToInitialInOnStartedFragment>("second")
+            fragment<EmptyFragment>("third")
+        }
+        navController.navigate("second")
+
+        val fm = supportFragmentManager.findFragmentById(R.id.nav_host)?.childFragmentManager
+        fm?.executePendingTransactions()
+
+        assertThat(navController.currentBackStackEntry?.destination?.route).isEqualTo("third")
+        assertThat(navController.visibleEntries.value).containsExactly(
+            navController.currentBackStackEntry
+        )
+    }
+
+    @Test
+    fun testPopInitialAndNavigateInitial() = withNavigationActivity {
+        navController.graph = navController.createGraph("first") {
+            fragment<EmptyFragment>("first")
+            fragment<EmptyFragment>("second")
+        }
+        val fm = supportFragmentManager.findFragmentById(R.id.nav_host)?.childFragmentManager
+        fm?.executePendingTransactions()
+
+        // pop first as initial entry, then navigate to second which is the new initial entry
+        navController.navigate(
+            "second",
+            navOptions { popUpTo("first") { inclusive = true } }
+        )
+
+        fm?.executePendingTransactions()
+
+        assertThat(navController.currentBackStackEntry?.destination?.route).isEqualTo("second")
+        assertThat(navController.visibleEntries.value).containsExactly(
+            navController.currentBackStackEntry
+        )
+        val navigator = navController.navigatorProvider.getNavigator(FragmentNavigator::class.java)
+        // the popUpTo and following navigation to second are both isolated
+        // fragment operations (initial entry) so neither of them would trigger a callback from FM
+        assertThat(navigator.pendingOps).isEmpty()
+    }
+
     @LargeTest
     @Test
     fun testSystemBackPressAfterPopUpToStartDestinationOffBackStack() = withNavigationActivity {
@@ -453,6 +498,17 @@
     }
 }
 
+class PopToInitialInOnStartedFragment : Fragment(R.layout.strict_view_fragment) {
+    override fun onStart() {
+        super.onStart()
+        findNavController().navigate("third") {
+            popUpTo("first") {
+                inclusive = true
+            }
+        }
+    }
+}
+
 class TestDialogFragment : DialogFragment() {
     val dialogs = mutableListOf<Dialog>()
 
diff --git a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.kt b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.kt
index cbc54c65..f58d911 100644
--- a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.kt
+++ b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.kt
@@ -338,7 +338,14 @@
             addPendingOps(incomingEntry.id)
         }
         // add pending ops here before any animation (if present) starts
-        poppedList.filter { it.id != initialEntry.id }.forEach { entry ->
+        poppedList.filter { entry ->
+            // normally we don't add initialEntry to pending ops because the adding/popping
+            // of an isolated fragment does not trigger onBackStackCommitted. But if initial
+            // entry was already added to pendingOps, it was likely an incomingEntry that now
+            // needs to be popped, so we need to overwrite isPop to true here.
+            pendingOps.asSequence().map { it.first }.contains(entry.id) ||
+                entry.id != initialEntry.id
+        }.forEach { entry ->
             addPendingOps(entry.id, isPop = true)
         }
 
diff --git a/playground-common/playground-plugin/build.gradle b/playground-common/playground-plugin/build.gradle
index 0d973f1..cc6735b 100644
--- a/playground-common/playground-plugin/build.gradle
+++ b/playground-common/playground-plugin/build.gradle
@@ -21,7 +21,7 @@
 
 dependencies {
     implementation(project(":shared"))
-    implementation("com.gradle:gradle-enterprise-gradle-plugin:3.15.1")
+    implementation("com.gradle:gradle-enterprise-gradle-plugin:3.16")
     implementation("com.gradle:common-custom-user-data-gradle-plugin:1.12")
     implementation("supportBuildSrc:private")
     implementation("supportBuildSrc:public")
diff --git a/privacysandbox/ui/ui-client/build.gradle b/privacysandbox/ui/ui-client/build.gradle
index 7c21fd7..d278278 100644
--- a/privacysandbox/ui/ui-client/build.gradle
+++ b/privacysandbox/ui/ui-client/build.gradle
@@ -30,6 +30,7 @@
 
     implementation("androidx.lifecycle:lifecycle-common:2.2.0")
     implementation("androidx.privacysandbox.sdkruntime:sdkruntime-client:1.0.0-alpha08")
+    implementation("androidx.customview:customview-poolingcontainer:1.0.0-alpha01")
     implementation project(path: ':privacysandbox:ui:ui-core')
 
     androidTestImplementation(project(":internal-testutils-runtime"))
diff --git a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt
index e81928d..2669711 100644
--- a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt
+++ b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/view/SandboxedSdkView.kt
@@ -33,6 +33,11 @@
 import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresApi
 import androidx.annotation.VisibleForTesting
+import androidx.customview.poolingcontainer.PoolingContainerListener
+import androidx.customview.poolingcontainer.addPoolingContainerListener
+import androidx.customview.poolingcontainer.isPoolingContainer
+import androidx.customview.poolingcontainer.isWithinPoolingContainer
+import androidx.customview.poolingcontainer.removePoolingContainerListener
 import androidx.privacysandbox.ui.client.view.SandboxedSdkUiSessionState.Active
 import androidx.privacysandbox.ui.client.view.SandboxedSdkUiSessionState.Idle
 import androidx.privacysandbox.ui.client.view.SandboxedSdkUiSessionState.Loading
@@ -99,6 +104,11 @@
 class SandboxedSdkView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
     ViewGroup(context, attrs) {
 
+    // TODO(b/284147223): Remove this logic in V+
+    private val surfaceView = SurfaceView(context).apply {
+        visibility = GONE
+    }
+
     // This will only be invoked when the content view has been set and the window is attached.
     private val surfaceChangedCallback = object : SurfaceHolder.Callback {
         override fun surfaceCreated(p0: SurfaceHolder) {
@@ -131,6 +141,8 @@
     private var previousHeight = -1
     private var currentClippingBounds = Rect()
     internal val stateListenerManager: StateListenerManager = StateListenerManager()
+    private var poolingContainerChild: View? = null
+    private var poolingContainerListener = PoolingContainerListener {}
 
     /**
      * Adds a state change listener to the UI session and immediately reports the current
@@ -319,6 +331,9 @@
     }
 
     override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+        if (this.isWithinPoolingContainer) {
+            attachPoolingContainerListener()
+        }
         // We will not call client?.notifyResized for the first onLayout call
         // and the case in which the width and the height remain unchanged.
         if ((previousWidth != (right - left) || previousHeight != (bottom - top)) &&
@@ -337,16 +352,51 @@
         checkClientOpenSession()
     }
 
-    override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-        CompatImpl.deriveInputTokenAndOpenSession(context, this)
-    }
-
-    override fun onDetachedFromWindow() {
+    private fun closeClient() {
         client?.close()
         client = null
         windowInputToken = null
         removeCallbacks()
+    }
+
+    private fun attachPoolingContainerListener() {
+        val listener = PoolingContainerListener {
+            closeClient()
+            poolingContainerChild
+                ?.removePoolingContainerListener(poolingContainerListener)
+        }
+
+        var currentView = this as View
+        var parentView = parent
+
+        while (parentView != null && !(parentView as View).isPoolingContainer) {
+            currentView = parentView
+            parentView = currentView.parent
+        }
+
+        if (currentView == poolingContainerChild) {
+            return
+        }
+
+        poolingContainerChild
+            ?.removePoolingContainerListener(poolingContainerListener)
+        currentView.addPoolingContainerListener(listener)
+        poolingContainerChild = currentView
+        poolingContainerListener = listener
+    }
+
+    override fun onAttachedToWindow() {
+        super.onAttachedToWindow()
+        if (this.isWithinPoolingContainer) {
+            attachPoolingContainerListener()
+        }
+        CompatImpl.deriveInputTokenAndOpenSession(context, this)
+    }
+
+    override fun onDetachedFromWindow() {
+        if (!this.isWithinPoolingContainer) {
+            closeClient()
+        }
         super.onDetachedFromWindow()
     }
 
diff --git a/privacysandbox/ui/ui-tests/build.gradle b/privacysandbox/ui/ui-tests/build.gradle
index 62b367f..76d7dae 100644
--- a/privacysandbox/ui/ui-tests/build.gradle
+++ b/privacysandbox/ui/ui-tests/build.gradle
@@ -27,6 +27,7 @@
     implementation project(path: ':privacysandbox:ui:ui-client')
     implementation project(path: ':privacysandbox:ui:ui-provider')
     implementation(libs.kotlinStdlib)
+    implementation 'androidx.recyclerview:recyclerview:1.3.2'
 
     androidTestImplementation(project(":internal-testutils-runtime"))
     androidTestImplementation(libs.junit)
@@ -38,6 +39,7 @@
     androidTestImplementation(libs.testRules)
     androidTestImplementation(libs.truth)
     androidTestImplementation project(path: ':appcompat:appcompat')
+    androidTestImplementation project(':recyclerview:recyclerview')
     def multidex_version = "2.0.1"
     implementation "androidx.multidex:multidex:$multidex_version"
 }
diff --git a/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/IntegrationTests.kt b/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/IntegrationTests.kt
index ec0dc24..bdd7717 100644
--- a/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/IntegrationTests.kt
+++ b/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/IntegrationTests.kt
@@ -35,6 +35,8 @@
 import androidx.privacysandbox.ui.core.BackwardCompatUtil
 import androidx.privacysandbox.ui.core.SandboxedUiAdapter
 import androidx.privacysandbox.ui.provider.toCoreLibInfo
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
 import androidx.test.ext.junit.rules.ActivityScenarioRule
 import androidx.test.filters.MediumTest
 import androidx.test.platform.app.InstrumentationRegistry
@@ -77,8 +79,10 @@
     private val context = InstrumentationRegistry.getInstrumentation().context
 
     private lateinit var view: SandboxedSdkView
+    private lateinit var recyclerView: RecyclerView
     private lateinit var stateChangeListener: TestStateChangeListener
     private lateinit var errorLatch: CountDownLatch
+    private lateinit var linearLayout: LinearLayout
 
     @Before
     fun setup() {
@@ -89,10 +93,11 @@
 
         activityScenarioRule.withActivity {
             view = SandboxedSdkView(context)
+            recyclerView = RecyclerView(context)
             errorLatch = CountDownLatch(1)
             stateChangeListener = TestStateChangeListener(errorLatch)
             view.addStateChangedListener(stateChangeListener)
-            val linearLayout = LinearLayout(context)
+            linearLayout = LinearLayout(context)
             linearLayout.layoutParams = LinearLayout.LayoutParams(
                 LinearLayout.LayoutParams.MATCH_PARENT,
                 LinearLayout.LayoutParams.MATCH_PARENT
@@ -100,6 +105,8 @@
             setContentView(linearLayout)
             view.layoutParams = LinearLayout.LayoutParams(INITIAL_WIDTH, INITIAL_HEIGHT)
             linearLayout.addView(view)
+            linearLayout.addView(recyclerView)
+            recyclerView.setLayoutManager(LinearLayoutManager(context))
         }
     }
 
@@ -309,6 +316,175 @@
         assertThat(client.hashCode()).isEqualTo(client.hashCode())
     }
 
+    @Test
+    fun testPoolingContainerListener_AllViewsRemovedFromContainer() {
+        // TODO(b/309848703): Stop skipping this for backwards compat flow
+        assumeTrue(!invokeBackwardsCompatFlow)
+
+        val adapter = createRecyclerViewTestAdapterAndWaitForChildrenToBeActive(
+            isNestedView = false)
+
+        activityScenarioRule.withActivity {
+            recyclerView.layoutManager!!.removeAllViews()
+        }
+
+        adapter.waitForViewsToBeDetached()
+        adapter.ensureChildrenDoNotBecomeIdleFromActive()
+    }
+
+    @Test
+    fun testPoolingContainerListener_ContainerRemovedFromLayout() {
+        // TODO(b/309848703): Stop skipping this for backwards compat flow
+        assumeTrue(!invokeBackwardsCompatFlow)
+
+        val adapter = createRecyclerViewTestAdapterAndWaitForChildrenToBeActive(
+            isNestedView = true)
+
+        activityScenarioRule.withActivity {
+            linearLayout.removeView(recyclerView)
+        }
+
+        adapter.ensureAllChildrenBecomeIdleFromActive()
+    }
+
+    @Test
+    fun testPoolingContainerListener_ViewWithinAnotherView_AllViewsRemovedFromContainer() {
+        // TODO(b/309848703): Stop skipping this for backwards compat flow
+        assumeTrue(!invokeBackwardsCompatFlow)
+
+        val adapter = createRecyclerViewTestAdapterAndWaitForChildrenToBeActive(
+            isNestedView = false)
+
+        activityScenarioRule.withActivity {
+            recyclerView.layoutManager!!.removeAllViews()
+        }
+
+        adapter.waitForViewsToBeDetached()
+        adapter.ensureChildrenDoNotBecomeIdleFromActive()
+    }
+
+    @Test
+    fun testPoolingContainerListener_ViewWithinAnotherView_ContainerRemovedFromLayout() {
+        // TODO(b/309848703): Stop skipping this for backwards compat flow
+        assumeTrue(!invokeBackwardsCompatFlow)
+
+        val adapter = createRecyclerViewTestAdapterAndWaitForChildrenToBeActive(
+            isNestedView = true)
+
+        activityScenarioRule.withActivity {
+            linearLayout.removeView(recyclerView)
+        }
+
+        adapter.ensureAllChildrenBecomeIdleFromActive()
+    }
+
+    fun createRecyclerViewTestAdapterAndWaitForChildrenToBeActive(isNestedView: Boolean):
+        RecyclerViewTestAdapter {
+        val adapter = RecyclerViewTestAdapter(context, isNestedView)
+        activityScenarioRule.withActivity {
+            recyclerView.setAdapter(adapter)
+        }
+
+        adapter.waitForViewsToBeAttached()
+
+        for (i in 0 until recyclerView.childCount) {
+            lateinit var childView: SandboxedSdkView
+            if (isNestedView) {
+                childView = (recyclerView.getChildAt(i) as ViewGroup)
+                    .getChildAt(0) as SandboxedSdkView
+            } else {
+                childView = recyclerView.getChildAt(i) as SandboxedSdkView
+            }
+            createAdapterAndWaitToBeActive(true, childView)
+        }
+
+        adapter.ensureAllChildrenBecomeActive()
+        return adapter
+    }
+
+    class RecyclerViewTestAdapter(
+        private val context: Context,
+        val isNestedView: Boolean = false,
+    ) :
+        RecyclerView.Adapter<RecyclerViewTestAdapter.ViewHolder>() {
+        class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
+        var numberOfSandboxedSdkViews = 0
+        val items = 5
+        private val activeLatch = CountDownLatch(items)
+        // The session will first be idle -> active -> idle in
+        // our tests, hence the count is items*2
+        private val idleLatch = CountDownLatch(items * 2)
+        private val attachedLatch = CountDownLatch(items)
+        private val detachedLatch = CountDownLatch(items)
+        val onAttachStateChangeListener = object : View.OnAttachStateChangeListener {
+            override fun onViewAttachedToWindow(v: View) {
+                attachedLatch.countDown()
+            }
+
+            override fun onViewDetachedFromWindow(v: View) {
+                if (attachedLatch.count.equals(0.toLong())) {
+                    detachedLatch.countDown()
+                }
+            }
+        }
+        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+            if (numberOfSandboxedSdkViews >= items) {
+                // We should return without creating a SandboxedSdkView if the
+                // number of SandboxedSdkViews is already equal to items. Recycler
+                // view will create new ViewHolders once SandboxedSdkViews are
+                // removed. We do not want to count latch down at that point of time.
+                return ViewHolder(View(context))
+            }
+
+            val listener = SandboxedSdkUiSessionStateChangedListener { state ->
+                if (state is SandboxedSdkUiSessionState.Active) {
+                    activeLatch.countDown()
+                } else if (state is SandboxedSdkUiSessionState.Idle) {
+                    idleLatch.countDown()
+                }
+            }
+
+            numberOfSandboxedSdkViews++
+            var view: View = SandboxedSdkView(context)
+            (view as SandboxedSdkView).addStateChangedListener(listener)
+            if (isNestedView) {
+                val parentView = LinearLayout(context)
+                parentView.addView(view)
+                view = parentView
+            }
+            view.layoutParams =
+                RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1)
+            view.addOnAttachStateChangeListener(onAttachStateChangeListener)
+            return ViewHolder(view)
+        }
+
+        fun waitForViewsToBeAttached() {
+            assertThat(attachedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+        }
+
+        fun waitForViewsToBeDetached() {
+            assertThat(detachedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+        }
+
+        fun ensureAllChildrenBecomeActive() {
+            assertThat(activeLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+        }
+
+        fun ensureAllChildrenBecomeIdleFromActive() {
+            assertThat(idleLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+        }
+
+        fun ensureChildrenDoNotBecomeIdleFromActive() {
+            assertThat(idleLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isFalse()
+            assertThat(idleLatch.count).isEqualTo(items)
+        }
+
+        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+        }
+
+        override fun getItemCount(): Int = items
+    }
+
     private fun getCoreLibInfoFromAdapter(sdkAdapter: SandboxedUiAdapter): Bundle {
         val bundle = sdkAdapter.toCoreLibInfo(context)
         bundle.putBoolean(TEST_ONLY_USE_REMOTE_ADAPTER, !invokeBackwardsCompatFlow)
@@ -322,10 +498,10 @@
      * created adapter is set on [view] to establish session.
      */
     private fun createAdapterAndEstablishSession(
-            hasFailingTestSession: Boolean = false,
-            viewForSession: SandboxedSdkView? = view,
-            testSessionClient: TestSessionClient = TestSessionClient()
-        ): TestSandboxedUiAdapter {
+        hasFailingTestSession: Boolean = false,
+        viewForSession: SandboxedSdkView? = view,
+        testSessionClient: TestSessionClient = TestSessionClient()
+    ): TestSandboxedUiAdapter {
 
         val adapter = TestSandboxedUiAdapter(hasFailingTestSession)
         val adapterFromCoreLibInfo = SandboxedUiAdapterFactory.createFromCoreLibInfo(
@@ -354,14 +530,17 @@
         return adapter
     }
 
-    private fun createAdapterAndWaitToBeActive(initialZOrder: Boolean = true):
+    private fun createAdapterAndWaitToBeActive(
+        initialZOrder: Boolean = true,
+        viewForSession: SandboxedSdkView = view
+    ):
         TestSandboxedUiAdapter {
-        view.orderProviderUiAboveClientUi(initialZOrder)
+        viewForSession.orderProviderUiAboveClientUi(initialZOrder)
 
-        val adapter = createAdapterAndEstablishSession()
+        val adapter = createAdapterAndEstablishSession(false, viewForSession)
 
         val activeLatch = CountDownLatch(1)
-        view.addStateChangedListener { state ->
+        viewForSession.addStateChangedListener { state ->
             if (state is SandboxedSdkUiSessionState.Active) {
                 activeLatch.countDown()
             }
diff --git a/room/integration-tests/testapp/build.gradle b/room/integration-tests/testapp/build.gradle
index d2f0759..cc25642 100644
--- a/room/integration-tests/testapp/build.gradle
+++ b/room/integration-tests/testapp/build.gradle
@@ -136,11 +136,9 @@
     androidTestImplementation(libs.testRules)
     androidTestImplementation(libs.espressoCore)
     androidTestImplementation(libs.truth)
-    androidTestImplementation(libs.mockitoCore4)
-    androidTestImplementation(libs.dexmakerMockito)
+    androidTestImplementation(libs.mockitoAndroid5)
     androidTestImplementation(project(":internal-testutils-truth"))
 
-
     testImplementation(libs.junit)
 }
 
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
index 0f01914..7b32220 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
@@ -1904,9 +1904,32 @@
             }
             """.trimIndent()
         )
+        val javaSrc = Source.java(
+            "JavaClass",
+            """
+            import java.util.List;
+            import kotlin.Result;
+            import kotlin.UInt;
+            interface JavaClass {
+                UInt inlineClassDirectUsage();
+                List<UInt> inlineClassIndirectUsage();
+                Result<Integer> genericInlineClassDirectUsage();
+                List<Result<Integer>> genericInlineClassIndirectUsage();
+
+                MyInlineClass customInlineClassDirectUsage();
+                List<MyInlineClass> customInlineClassIndirectUsage();
+                MyGenericInlineClass<Integer> customGenericInlineClassDirectUsage();
+                List<MyGenericInlineClass<Integer>> customGenericInlineClassIndirectUsage();
+            }
+            """.trimIndent()
+        )
         runProcessorTest(
-            sources = if (isPrecompiled) { emptyList() } else { listOf(kotlinSrc) },
-            classpath = if (isPrecompiled) { compileFiles(listOf(kotlinSrc)) } else { emptyList() }
+            sources = if (isPrecompiled) { emptyList() } else { listOf(kotlinSrc, javaSrc) },
+            classpath = if (isPrecompiled) {
+                compileFiles(listOf(kotlinSrc, javaSrc))
+            } else {
+                emptyList()
+            }
         ) { invocation ->
             val kotlinElm = invocation.processingEnv.requireTypeElement("KotlinClass")
             kotlinElm.getMethodByJvmName("kotlinValueClassDirectUsage").apply {
@@ -1989,6 +2012,53 @@
                         .isEqualTo("kotlin.collections.List<MyGenericInlineClass<kotlin.Int>>")
                 }
             }
+
+            val javaElm = invocation.processingEnv.requireTypeElement("JavaClass")
+            javaElm.getMethodByJvmName("inlineClassDirectUsage").apply {
+                if (invocation.isKsp) {
+                    // TODO(kuanyingchou): When an inline type is used in Java we shouldn't replace
+                    // it with the JVM type.
+                    assertThat(returnType.asTypeName().java.toString())
+                        .isEqualTo("int")
+                } else {
+                    assertThat(returnType.asTypeName().java.toString())
+                        .isEqualTo("kotlin.UInt")
+                }
+            }
+            javaElm.getMethodByJvmName("inlineClassIndirectUsage").apply {
+                assertThat(returnType.asTypeName().java.toString())
+                    .isEqualTo("java.util.List<kotlin.UInt>")
+            }
+            javaElm.getMethodByJvmName("customInlineClassDirectUsage").apply {
+                if (invocation.isKsp) {
+                    // TODO(kuanyingchou): When an inline type is used in Java we shouldn't replace
+                    // it with the JVM type.
+                    assertThat(returnType.asTypeName().java.toString())
+                        .isEqualTo("int")
+                } else {
+                    assertThat(returnType.asTypeName().java.toString())
+                        .isEqualTo("MyInlineClass")
+                }
+            }
+            javaElm.getMethodByJvmName("customInlineClassIndirectUsage").apply {
+                assertThat(returnType.asTypeName().java.toString())
+                    .isEqualTo("java.util.List<MyInlineClass>")
+            }
+            javaElm.getMethodByJvmName("customGenericInlineClassDirectUsage").apply {
+                if (invocation.isKsp) {
+                    // TODO(kuanyingchou): When an inline type is used in Java we shouldn't replace
+                    // it with the JVM type.
+                    assertThat(returnType.asTypeName().java.toString())
+                        .isEqualTo("java.lang.Number")
+                } else {
+                    assertThat(returnType.asTypeName().java.toString())
+                        .isEqualTo("MyGenericInlineClass<java.lang.Integer>")
+                }
+            }
+            javaElm.getMethodByJvmName("customGenericInlineClassIndirectUsage").apply {
+                assertThat(returnType.asTypeName().java.toString())
+                    .isEqualTo("java.util.List<MyGenericInlineClass<java.lang.Integer>>")
+            }
         }
     }
 }
diff --git a/settings.gradle b/settings.gradle
index b60f210..6d5af64 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -30,7 +30,7 @@
         classpath("com.google.protobuf:protobuf-java:3.22.3")
         // upgrade okio for gcpbuildcache that is compatible with the wire plugin used by androidx
         classpath("com.squareup.okio:okio:3.3.0")
-        classpath("com.gradle:gradle-enterprise-gradle-plugin:3.15.1")
+        classpath("com.gradle:gradle-enterprise-gradle-plugin:3.16")
         classpath("com.gradle:common-custom-user-data-gradle-plugin:1.12")
         classpath("androidx.build.gradle.gcpbuildcache:gcpbuildcache:1.0.0-beta05")
     }
@@ -505,7 +505,6 @@
 includeProject(":compose:material3:benchmark", [BuildType.COMPOSE])
 includeProject(":compose:material3:material3-adaptive", [BuildType.COMPOSE])
 includeProject(":compose:material3:material3-adaptive:material3-adaptive-samples", "compose/material3/material3-adaptive/samples", [BuildType.COMPOSE])
-includeProject(":compose:material3:material3-adaptive:material3-adaptive-benchmark", "compose/material3/material3-adaptive/benchmark", [BuildType.COMPOSE])
 includeProject(":compose:material3:material3-adaptive-navigation-suite", [BuildType.COMPOSE])
 includeProject(":compose:material3:material3-adaptive-navigation-suite:material3-adaptive-navigation-suite-samples", "compose/material3/material3-adaptive-navigation-suite/samples", [BuildType.COMPOSE])
 includeProject(":compose:material3:material3-common", [BuildType.COMPOSE])
diff --git a/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt b/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt
index f5ff489..abea24a 100644
--- a/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt
+++ b/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.kt
@@ -48,16 +48,12 @@
 import androidx.core.view.AccessibilityDelegateCompat
 import androidx.core.view.ViewCompat
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
-import androidx.core.view.animation.PathInterpolatorCompat
 import androidx.core.view.forEach
 import androidx.core.view.forEachIndexed
 import androidx.customview.view.AbsSavedState
 import androidx.customview.widget.Openable
 import androidx.customview.widget.ViewDragHelper
 import androidx.slidingpanelayout.R
-import androidx.transition.ChangeBounds
-import androidx.transition.Transition
-import androidx.transition.TransitionManager
 import androidx.window.layout.FoldingFeature
 import androidx.window.layout.WindowInfoTracker
 import java.util.concurrent.CopyOnWriteArrayList
@@ -342,6 +338,12 @@
     internal annotation class LockMode
 
     private var foldingFeature: FoldingFeature? = null
+        set(value) {
+            if (value != field) {
+                field = value
+                requestLayout()
+            }
+        }
 
     /**
      * [Job] that tracks the last launched coroutine running [whileAttachedToVisibleWindow].
@@ -780,12 +782,6 @@
             .distinctUntilChanged()
             .collect { nextFeature ->
                 foldingFeature = nextFeature
-                // Start transition animation when folding feature changed
-                val changeBounds: Transition = ChangeBounds()
-                changeBounds.duration = 300L
-                changeBounds.interpolator = PathInterpolatorCompat.create(0.2f, 0f, 0f, 1f)
-                TransitionManager.beginDelayedTransition(this@SlidingPaneLayout, changeBounds)
-                requestLayout()
             }
     }
 
diff --git a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java
index 94338f3..0ecbc8d 100644
--- a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java
+++ b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java
@@ -140,7 +140,7 @@
 
         UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
         mDevice.pressMenu();
-        assertEquals("keycode menu pressed", textView.getText());
+        assertTrue(textView.wait(Until.textEquals("keycode menu pressed"), TIMEOUT_MS));
     }
 
     @Test
@@ -149,7 +149,7 @@
 
         UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
         mDevice.pressBack();
-        assertEquals("keycode back pressed", textView.getText());
+        assertTrue(textView.wait(Until.textEquals("keycode back pressed"), TIMEOUT_MS));
     }
 
     @Test
@@ -158,7 +158,7 @@
 
         UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
         mDevice.pressSearch();
-        assertEquals("keycode search pressed", textView.getText());
+        assertTrue(textView.wait(Until.textEquals("keycode search pressed"), TIMEOUT_MS));
     }
 
     @Test
@@ -167,7 +167,7 @@
 
         UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
         mDevice.pressDPadCenter();
-        assertEquals("keycode dpad center pressed", textView.getText());
+        assertTrue(textView.wait(Until.textEquals("keycode dpad center pressed"), TIMEOUT_MS));
     }
 
     @Test
@@ -176,7 +176,7 @@
 
         UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
         mDevice.pressDPadDown();
-        assertEquals("keycode dpad down pressed", textView.getText());
+        assertTrue(textView.wait(Until.textEquals("keycode dpad down pressed"), TIMEOUT_MS));
     }
 
     @Test
@@ -185,7 +185,7 @@
 
         UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
         mDevice.pressDPadUp();
-        assertEquals("keycode dpad up pressed", textView.getText());
+        assertTrue(textView.wait(Until.textEquals("keycode dpad up pressed"), TIMEOUT_MS));
     }
 
     @Test
@@ -194,7 +194,7 @@
 
         UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
         mDevice.pressDPadLeft();
-        assertEquals("keycode dpad left pressed", textView.getText());
+        assertTrue(textView.wait(Until.textEquals("keycode dpad left pressed"), TIMEOUT_MS));
     }
 
     @Test
@@ -203,7 +203,7 @@
 
         UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
         mDevice.pressDPadRight();
-        assertEquals("keycode dpad right pressed", textView.getText());
+        assertTrue(textView.wait(Until.textEquals("keycode dpad right pressed"), TIMEOUT_MS));
     }
 
     @Test
@@ -212,7 +212,7 @@
 
         UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
         mDevice.pressDelete();
-        assertEquals("keycode delete pressed", textView.getText());
+        assertTrue(textView.wait(Until.textEquals("keycode delete pressed"), TIMEOUT_MS));
     }
 
     @Test
@@ -221,7 +221,7 @@
 
         UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
         mDevice.pressEnter();
-        assertEquals("keycode enter pressed", textView.getText());
+        assertTrue(textView.wait(Until.textEquals("keycode enter pressed"), TIMEOUT_MS));
     }
 
     @Test
@@ -230,7 +230,7 @@
 
         UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
         mDevice.pressKeyCode(KeyEvent.KEYCODE_0);
-        assertEquals("keycode 0 pressed", textView.getText());
+        assertTrue(textView.wait(Until.textEquals("keycode 0 pressed"), TIMEOUT_MS));
     }
 
     @Test
@@ -240,7 +240,8 @@
         UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
         mDevice.pressKeyCode(KeyEvent.KEYCODE_Z,
                 KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_ON);
-        assertEquals("keycode Z pressed with meta shift left on", textView.getText());
+        assertTrue(textView.wait(Until.textEquals("keycode Z pressed with meta shift left on"),
+                TIMEOUT_MS));
     }
 
     @Test
@@ -260,7 +261,8 @@
 
         UiObject2 textView = mDevice.findObject(By.res(TEST_APP, "text_view"));
         mDevice.pressKeyCodes(new int[]{KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_B});
-        assertEquals("keycode A and keycode B are pressed", textView.getText());
+        assertTrue(textView.wait(Until.textEquals("keycode A and keycode B are pressed"),
+                TIMEOUT_MS));
     }
 
     @Test
diff --git a/test/uiautomator/uiautomator/api/2.3.0-beta01.txt b/test/uiautomator/uiautomator/api/2.3.0-beta01.txt
index bfaecd4..d3d9df8 100644
--- a/test/uiautomator/uiautomator/api/2.3.0-beta01.txt
+++ b/test/uiautomator/uiautomator/api/2.3.0-beta01.txt
@@ -345,9 +345,9 @@
     method public <U> U! scrollUntil(androidx.test.uiautomator.Direction, androidx.test.uiautomator.Condition<? super androidx.test.uiautomator.UiObject2,U!>);
     method public <U> U! scrollUntil(androidx.test.uiautomator.Direction, androidx.test.uiautomator.EventCondition<U!>);
     method public void setGestureMargin(int);
-    method public void setGestureMarginPercent(@FloatRange(from=0.0f, to=0.5f) float);
-    method public void setGestureMarginPercent(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float);
+    method public void setGestureMarginPercentage(@FloatRange(from=0.0f, to=0.5f) float);
     method public void setGestureMargins(int, int, int, int);
+    method public void setGestureMarginsPercentage(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float);
     method public void setText(String?);
     method public void swipe(androidx.test.uiautomator.Direction, float);
     method public void swipe(androidx.test.uiautomator.Direction, float, int);
diff --git a/test/uiautomator/uiautomator/api/api_lint.ignore b/test/uiautomator/uiautomator/api/api_lint.ignore
index 1c65a590..a45a44b 100644
--- a/test/uiautomator/uiautomator/api/api_lint.ignore
+++ b/test/uiautomator/uiautomator/api/api_lint.ignore
@@ -35,6 +35,8 @@
     Missing nullability on method `getText` return
 
 
+PercentageInt: androidx.test.uiautomator.UiObject2#setGestureMarginPercentage(float):
+    Percentage must use ints, was `float` in `setGestureMarginPercentage`
 PercentageInt: androidx.test.uiautomator.UiScrollable#getSwipeDeadZonePercentage():
     Percentage must use ints, was `double` in `getSwipeDeadZonePercentage`
 
diff --git a/test/uiautomator/uiautomator/api/current.ignore b/test/uiautomator/uiautomator/api/current.ignore
index 5f145ac..3d43de2 100644
--- a/test/uiautomator/uiautomator/api/current.ignore
+++ b/test/uiautomator/uiautomator/api/current.ignore
@@ -1,7 +1,11 @@
 // Baseline format: 1.0
-AddedAbstractMethod: androidx.test.uiautomator.EventCondition#getResult():
-    Added method androidx.test.uiautomator.EventCondition.getResult()
+AddedMethod: androidx.test.uiautomator.UiObject2#setGestureMarginPercentage(float):
+    Added method androidx.test.uiautomator.UiObject2.setGestureMarginPercentage(float)
+AddedMethod: androidx.test.uiautomator.UiObject2#setGestureMarginsPercentage(float, float, float, float):
+    Added method androidx.test.uiautomator.UiObject2.setGestureMarginsPercentage(float,float,float,float)
 
 
-RemovedMethod: androidx.test.uiautomator.Until#Until():
-    Removed constructor androidx.test.uiautomator.Until()
+RemovedMethod: androidx.test.uiautomator.UiObject2#setGestureMarginPercent(float):
+    Removed method androidx.test.uiautomator.UiObject2.setGestureMarginPercent(float)
+RemovedMethod: androidx.test.uiautomator.UiObject2#setGestureMarginPercent(float, float, float, float):
+    Removed method androidx.test.uiautomator.UiObject2.setGestureMarginPercent(float,float,float,float)
diff --git a/test/uiautomator/uiautomator/api/current.txt b/test/uiautomator/uiautomator/api/current.txt
index bfaecd4..d3d9df8 100644
--- a/test/uiautomator/uiautomator/api/current.txt
+++ b/test/uiautomator/uiautomator/api/current.txt
@@ -345,9 +345,9 @@
     method public <U> U! scrollUntil(androidx.test.uiautomator.Direction, androidx.test.uiautomator.Condition<? super androidx.test.uiautomator.UiObject2,U!>);
     method public <U> U! scrollUntil(androidx.test.uiautomator.Direction, androidx.test.uiautomator.EventCondition<U!>);
     method public void setGestureMargin(int);
-    method public void setGestureMarginPercent(@FloatRange(from=0.0f, to=0.5f) float);
-    method public void setGestureMarginPercent(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float);
+    method public void setGestureMarginPercentage(@FloatRange(from=0.0f, to=0.5f) float);
     method public void setGestureMargins(int, int, int, int);
+    method public void setGestureMarginsPercentage(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float);
     method public void setText(String?);
     method public void swipe(androidx.test.uiautomator.Direction, float);
     method public void swipe(androidx.test.uiautomator.Direction, float, int);
diff --git a/test/uiautomator/uiautomator/api/restricted_2.3.0-beta01.txt b/test/uiautomator/uiautomator/api/restricted_2.3.0-beta01.txt
index bfaecd4..d3d9df8 100644
--- a/test/uiautomator/uiautomator/api/restricted_2.3.0-beta01.txt
+++ b/test/uiautomator/uiautomator/api/restricted_2.3.0-beta01.txt
@@ -345,9 +345,9 @@
     method public <U> U! scrollUntil(androidx.test.uiautomator.Direction, androidx.test.uiautomator.Condition<? super androidx.test.uiautomator.UiObject2,U!>);
     method public <U> U! scrollUntil(androidx.test.uiautomator.Direction, androidx.test.uiautomator.EventCondition<U!>);
     method public void setGestureMargin(int);
-    method public void setGestureMarginPercent(@FloatRange(from=0.0f, to=0.5f) float);
-    method public void setGestureMarginPercent(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float);
+    method public void setGestureMarginPercentage(@FloatRange(from=0.0f, to=0.5f) float);
     method public void setGestureMargins(int, int, int, int);
+    method public void setGestureMarginsPercentage(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float);
     method public void setText(String?);
     method public void swipe(androidx.test.uiautomator.Direction, float);
     method public void swipe(androidx.test.uiautomator.Direction, float, int);
diff --git a/test/uiautomator/uiautomator/api/restricted_current.ignore b/test/uiautomator/uiautomator/api/restricted_current.ignore
index 5f145ac..3d43de2 100644
--- a/test/uiautomator/uiautomator/api/restricted_current.ignore
+++ b/test/uiautomator/uiautomator/api/restricted_current.ignore
@@ -1,7 +1,11 @@
 // Baseline format: 1.0
-AddedAbstractMethod: androidx.test.uiautomator.EventCondition#getResult():
-    Added method androidx.test.uiautomator.EventCondition.getResult()
+AddedMethod: androidx.test.uiautomator.UiObject2#setGestureMarginPercentage(float):
+    Added method androidx.test.uiautomator.UiObject2.setGestureMarginPercentage(float)
+AddedMethod: androidx.test.uiautomator.UiObject2#setGestureMarginsPercentage(float, float, float, float):
+    Added method androidx.test.uiautomator.UiObject2.setGestureMarginsPercentage(float,float,float,float)
 
 
-RemovedMethod: androidx.test.uiautomator.Until#Until():
-    Removed constructor androidx.test.uiautomator.Until()
+RemovedMethod: androidx.test.uiautomator.UiObject2#setGestureMarginPercent(float):
+    Removed method androidx.test.uiautomator.UiObject2.setGestureMarginPercent(float)
+RemovedMethod: androidx.test.uiautomator.UiObject2#setGestureMarginPercent(float, float, float, float):
+    Removed method androidx.test.uiautomator.UiObject2.setGestureMarginPercent(float,float,float,float)
diff --git a/test/uiautomator/uiautomator/api/restricted_current.txt b/test/uiautomator/uiautomator/api/restricted_current.txt
index bfaecd4..d3d9df8 100644
--- a/test/uiautomator/uiautomator/api/restricted_current.txt
+++ b/test/uiautomator/uiautomator/api/restricted_current.txt
@@ -345,9 +345,9 @@
     method public <U> U! scrollUntil(androidx.test.uiautomator.Direction, androidx.test.uiautomator.Condition<? super androidx.test.uiautomator.UiObject2,U!>);
     method public <U> U! scrollUntil(androidx.test.uiautomator.Direction, androidx.test.uiautomator.EventCondition<U!>);
     method public void setGestureMargin(int);
-    method public void setGestureMarginPercent(@FloatRange(from=0.0f, to=0.5f) float);
-    method public void setGestureMarginPercent(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float);
+    method public void setGestureMarginPercentage(@FloatRange(from=0.0f, to=0.5f) float);
     method public void setGestureMargins(int, int, int, int);
+    method public void setGestureMarginsPercentage(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float);
     method public void setText(String?);
     method public void swipe(androidx.test.uiautomator.Direction, float);
     method public void swipe(androidx.test.uiautomator.Direction, float, int);
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java
index 548eee3..74c2c2c 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiObject2.java
@@ -151,26 +151,26 @@
      * Sets the percentage of gestures' margins to avoid touching too close to the edges, e.g.
      * when scrolling up, phone open quick settings instead if gesture is close to the top.
      * The percentage is based on the object's visible size, e.g. to set 20% margins:
-     * <pre>mUiObject2.setGestureMarginPercent(0.2f);</pre>
+     * <pre>mUiObject2.setGestureMarginPercentage(0.2f);</pre>
      *
      * @Param percent Float between [0, 0.5] for four margins: left, top, right, and bottom.
      */
-    public void setGestureMarginPercent(@FloatRange(from = 0f, to = 0.5f) float percent) {
-        setGestureMarginPercent(percent, percent, percent, percent);
+    public void setGestureMarginPercentage(@FloatRange(from = 0f, to = 0.5f) float percent) {
+        setGestureMarginsPercentage(percent, percent, percent, percent);
     }
 
     /**
      * Sets the percentage of gestures' margins to avoid touching too close to the edges, e.g.
      * when scrolling up, phone open quick settings instead if gesture is close to the top.
      * The percentage is based on the object's visible size, e.g. to set 20% bottom margin only:
-     * <pre>mUiObject2.setGestureMarginPercent(0f, 0f, 0f, 0.2f);</pre>
+     * <pre>mUiObject2.setGestureMarginsPercentage(0f, 0f, 0f, 0.2f);</pre>
      *
      * @Param left Float between [0, 1] for left margin
      * @Param top Float between [0, 1] for top margin
      * @Param right Float between [0, 1] for right margin
      * @Param bottom Float between [0, 1] for bottom margin
      */
-    public void setGestureMarginPercent(@FloatRange(from = 0f, to = 1f) float left,
+    public void setGestureMarginsPercentage(@FloatRange(from = 0f, to = 1f) float left,
             @FloatRange(from = 0f, to = 1f) float top,
             @FloatRange(from = 0f, to = 1f) float right,
             @FloatRange(from = 0f, to = 1f) float bottom) {
diff --git a/vectordrawable/vectordrawable-animated/build.gradle b/vectordrawable/vectordrawable-animated/build.gradle
index db008c9..32733ad0 100644
--- a/vectordrawable/vectordrawable-animated/build.gradle
+++ b/vectordrawable/vectordrawable-animated/build.gradle
@@ -8,7 +8,7 @@
 dependencies {
     api("androidx.annotation:annotation:1.2.0")
     api(project(":vectordrawable:vectordrawable"))
-    implementation(project(":core:core"))
+    implementation("androidx.core:core:1.12.0")
     implementation("androidx.interpolator:interpolator:1.0.0")
     implementation("androidx.collection:collection:1.1.0")
 
diff --git a/vectordrawable/vectordrawable-seekable/build.gradle b/vectordrawable/vectordrawable-seekable/build.gradle
index 3516876..928385c 100644
--- a/vectordrawable/vectordrawable-seekable/build.gradle
+++ b/vectordrawable/vectordrawable-seekable/build.gradle
@@ -7,7 +7,7 @@
 
 dependencies {
     api(project(":vectordrawable:vectordrawable"))
-    api(project(":core:core-animation"))
+    api("androidx.core:core-animation:1.0.0-rc01")
     implementation("androidx.collection:collection:1.1.0")
 
     androidTestImplementation(libs.testExtJunit)
diff --git a/wear/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/wear/benchmark/integration/macrobenchmark/SwipeBenchmark.kt b/wear/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/wear/benchmark/integration/macrobenchmark/SwipeBenchmark.kt
index df1706c..5a47931 100644
--- a/wear/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/wear/benchmark/integration/macrobenchmark/SwipeBenchmark.kt
+++ b/wear/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/wear/benchmark/integration/macrobenchmark/SwipeBenchmark.kt
@@ -27,6 +27,7 @@
 import androidx.test.uiautomator.UiDevice
 import androidx.testutils.createCompilationParams
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -34,6 +35,7 @@
 
 @LargeTest
 @RunWith(Parameterized::class)
+@Ignore("b/315170517")
 class SwipeBenchmark(
     private val compilationMode: CompilationMode
 ) {
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/BoolNodes.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/BoolNodes.java
index 2cdde6f..72e0f87 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/BoolNodes.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/BoolNodes.java
@@ -16,8 +16,6 @@
 
 package androidx.wear.protolayout.expression.pipeline;
 
-import android.util.Log;
-
 import androidx.annotation.UiThread;
 import androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool;
 import androidx.wear.protolayout.expression.proto.DynamicProto;
@@ -99,10 +97,13 @@
                                 return unboxedLhs > unboxedRhs;
                             case COMPARISON_OP_TYPE_GREATER_THAN_OR_EQUAL_TO:
                                 return unboxedLhs >= unboxedRhs;
-                            default:
-                                Log.e(TAG, "Unknown operation type in ComparisonInt32Node");
-                                return false;
+                            case COMPARISON_OP_TYPE_UNDEFINED:
+                            case UNRECOGNIZED:
+                                break;
                         }
+                        throw new IllegalArgumentException(
+                                "Unknown operation type in ComparisonInt32Node: "
+                                        + protoNode.getOperationType());
                     });
         }
     }
@@ -138,10 +139,13 @@
                             case COMPARISON_OP_TYPE_GREATER_THAN_OR_EQUAL_TO:
                                 return (unboxedLhs > unboxedRhs)
                                         || equalFloats(unboxedLhs, unboxedRhs);
-                            default:
-                                Log.e(TAG, "Unknown operation type in ComparisonInt32Node");
-                                return false;
+                            case COMPARISON_OP_TYPE_UNDEFINED:
+                            case UNRECOGNIZED:
+                                break;
                         }
+                        throw new IllegalArgumentException(
+                                "Unknown operation type in ComparisonFloatNode: "
+                                        + protoNode.getOperationType());
                     });
         }
 
@@ -176,10 +180,13 @@
                                 return a.equals(b);
                             case LOGICAL_OP_TYPE_NOT_EQUAL:
                                 return !a.equals(b);
-                            default:
-                                Log.e(TAG, "Unknown operation type in LogicalBoolOp");
-                                return false;
+                            case LOGICAL_OP_TYPE_UNDEFINED:
+                            case UNRECOGNIZED:
+                                break;
                         }
+                        throw new IllegalArgumentException(
+                                "Unknown operation type in LogicalBoolOp: "
+                                        + protoNode.getOperationType());
                     });
         }
     }
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeBindingRequest.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeBindingRequest.java
index 1af5943..b805b3f 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeBindingRequest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeBindingRequest.java
@@ -5,7 +5,7 @@
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *    http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -19,6 +19,7 @@
 import android.icu.util.ULocale;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.RestrictTo.Scope;
 import androidx.wear.protolayout.expression.DynamicBuilders;
@@ -36,6 +37,7 @@
 
 /**
  * Holds the parameters needed by {@link DynamicTypeEvaluator#bind}. It can be used as follows:
+ *
  * <pre>{@code
  * DynamicTypeBindingRequest request = DynamicTypeBindingRequest.forDynamicInt32(source,consumer);
  * BoundDynamicType boundType = evaluator.bind(request);
@@ -53,7 +55,7 @@
    *
    * @param floatSource The given float dynamic type that should be evaluated.
    * @param consumer The registered consumer for results of the evaluation. It will be called from
-   *     UI thread.
+   *   UI thread.
    */
   @NonNull
   @RestrictTo(Scope.LIBRARY_GROUP)
@@ -63,11 +65,11 @@
   }
 
   /**
-   * Creates a {@link DynamicTypeBindingRequest} from the given {@link DynamicBuilders.DynamicFloat}
-   * for binding.
+   * Creates a {@link DynamicTypeBindingRequest} from the given {@link
+   * DynamicBuilders.DynamicFloat} for binding.
    *
-   * <p>Results of evaluation will be sent through the given {@link DynamicTypeValueReceiver} on the
-   * given {@link Executor}.
+   * <p>Results of evaluation will be sent through the given {@link DynamicTypeValueReceiver} on
+   * the given {@link Executor}.
    *
    * @param floatSource The given float dynamic type that should be evaluated.
    * @param executor The Executor to run the consumer on.
@@ -78,7 +80,8 @@
       @NonNull DynamicBuilders.DynamicFloat floatSource,
       @NonNull Executor executor,
       @NonNull DynamicTypeValueReceiver<Float> consumer) {
-    return new DynamicFloatBindingRequestWithExecutor(floatSource, executor, consumer);
+    return new DynamicFloatBindingRequest(
+        floatSource.toDynamicFloatProto(), executor, consumer);
   }
 
   /**
@@ -87,21 +90,22 @@
    *
    * @param int32Source The given integer dynamic type that should be evaluated.
    * @param consumer The registered consumer for results of the evaluation. It will be called from
-   *     UI thread.
+   *   UI thread.
    */
   @NonNull
   @RestrictTo(Scope.LIBRARY_GROUP)
   public static DynamicTypeBindingRequest forDynamicInt32Internal(
-      @NonNull DynamicInt32 int32Source, @NonNull DynamicTypeValueReceiver<Integer> consumer) {
+      @NonNull DynamicInt32 int32Source,
+      @NonNull DynamicTypeValueReceiver<Integer> consumer) {
     return new DynamicInt32BindingRequest(int32Source, consumer);
   }
 
   /**
-   * Creates a {@link DynamicTypeBindingRequest} from the given {@link DynamicBuilders.DynamicInt32}
-   * for binding.
+   * Creates a {@link DynamicTypeBindingRequest} from the given {@link
+   * DynamicBuilders.DynamicInt32} for binding.
    *
-   * <p>Results of evaluation will be sent through the given {@link DynamicTypeValueReceiver} on the
-   * given {@link Executor}.
+   * <p>Results of evaluation will be sent through the given {@link DynamicTypeValueReceiver} on
+   * the given {@link Executor}.
    *
    * @param int32Source The given integer dynamic type that should be evaluated.
    * @param executor The Executor to run the consumer on.
@@ -112,7 +116,8 @@
       @NonNull DynamicBuilders.DynamicInt32 int32Source,
       @NonNull Executor executor,
       @NonNull DynamicTypeValueReceiver<Integer> consumer) {
-    return new DynamicInt32BindingRequestWithExecutor(int32Source, executor, consumer);
+    return new DynamicInt32BindingRequest(
+        int32Source.toDynamicInt32Proto(), executor, consumer);
   }
 
   /**
@@ -121,21 +126,22 @@
    *
    * @param colorSource The given color dynamic type that should be evaluated.
    * @param consumer The registered consumer for results of the evaluation. It will be called from
-   *     UI thread.
+   *   UI thread.
    */
   @NonNull
   @RestrictTo(Scope.LIBRARY_GROUP)
   public static DynamicTypeBindingRequest forDynamicColorInternal(
-      @NonNull DynamicColor colorSource, @NonNull DynamicTypeValueReceiver<Integer> consumer) {
+      @NonNull DynamicColor colorSource,
+      @NonNull DynamicTypeValueReceiver<Integer> consumer) {
     return new DynamicColorBindingRequest(colorSource, consumer);
   }
 
   /**
-   * Creates a {@link DynamicTypeBindingRequest} from the given {@link DynamicBuilders.DynamicColor}
-   * for binding.
+   * Creates a {@link DynamicTypeBindingRequest} from the given {@link
+   * DynamicBuilders.DynamicColor} for binding.
    *
-   * <p>Results of evaluation will be sent through the given {@link DynamicTypeValueReceiver} on the
-   * given {@link Executor}.
+   * <p>Results of evaluation will be sent through the given {@link DynamicTypeValueReceiver} on
+   * the given {@link Executor}.
    *
    * @param colorSource The given color dynamic type that should be evaluated.
    * @param executor The Executor to run the consumer on.
@@ -146,7 +152,8 @@
       @NonNull DynamicBuilders.DynamicColor colorSource,
       @NonNull Executor executor,
       @NonNull DynamicTypeValueReceiver<Integer> consumer) {
-    return new DynamicColorBindingRequestWithExecutor(colorSource, executor, consumer);
+    return new DynamicColorBindingRequest(
+        colorSource.toDynamicColorProto(), executor, consumer);
   }
 
   /**
@@ -155,7 +162,7 @@
    *
    * @param boolSource The given boolean dynamic type that should be evaluated.
    * @param consumer The registered consumer for results of the evaluation. It will be called from
-   *     UI thread.
+   *   UI thread.
    */
   @NonNull
   @RestrictTo(Scope.LIBRARY_GROUP)
@@ -165,11 +172,11 @@
   }
 
   /**
-   * Creates a {@link DynamicTypeBindingRequest} from the given {@link DynamicBuilders.DynamicBool}
-   * for binding.
+   * Creates a {@link DynamicTypeBindingRequest} from the given {@link
+   * DynamicBuilders.DynamicBool} for binding.
    *
-   * <p>Results of evaluation will be sent through the given {@link DynamicTypeValueReceiver} on the
-   * given {@link Executor}.
+   * <p>Results of evaluation will be sent through the given {@link DynamicTypeValueReceiver} on
+   * the given {@link Executor}.
    *
    * @param boolSource The given boolean dynamic type that should be evaluated.
    * @param executor The Executor to run the consumer on.
@@ -180,7 +187,7 @@
       @NonNull DynamicBuilders.DynamicBool boolSource,
       @NonNull Executor executor,
       @NonNull DynamicTypeValueReceiver<Boolean> consumer) {
-    return new DynamicBoolBindingRequestWithExecutor(boolSource, executor, consumer);
+    return new DynamicBoolBindingRequest(boolSource.toDynamicBoolProto(), executor, consumer);
   }
 
   /**
@@ -189,7 +196,7 @@
    *
    * @param stringSource The given String dynamic type that should be evaluated.
    * @param consumer The registered consumer for results of the evaluation. It will be called from
-   *     UI thread.
+   *   UI thread.
    * @param locale The locale used for the given String source.
    */
   @NonNull
@@ -205,8 +212,8 @@
    * Creates a {@link DynamicTypeBindingRequest} from the given {@link
    * DynamicBuilders.DynamicString} for binding.
    *
-   * <p>Results of evaluation will be sent through the given {@link DynamicTypeValueReceiver} on the
-   * given {@link Executor}.
+   * <p>Results of evaluation will be sent through the given {@link DynamicTypeValueReceiver} on
+   * the given {@link Executor}.
    *
    * @param stringSource The given String dynamic type that should be evaluated.
    * @param locale The locale used for the given String source.
@@ -219,7 +226,8 @@
       @NonNull ULocale locale,
       @NonNull Executor executor,
       @NonNull DynamicTypeValueReceiver<String> consumer) {
-    return new DynamicStringBindingRequestWithExecutor(stringSource, locale, executor, consumer);
+    return new DynamicStringBindingRequest(
+        stringSource.toDynamicStringProto(), locale, executor, consumer);
   }
 
   /**
@@ -228,7 +236,7 @@
    *
    * @param durationSource The given durations dynamic type that should be evaluated.
    * @param consumer The registered consumer for results of the evaluation. It will be called from
-   *     UI thread.
+   *   UI thread.
    */
   @NonNull
   @RestrictTo(Scope.LIBRARY_GROUP)
@@ -242,8 +250,8 @@
    * Creates a {@link DynamicTypeBindingRequest} from the given {@link
    * DynamicBuilders.DynamicDuration} for binding.
    *
-   * <p>Results of evaluation will be sent through the given {@link DynamicTypeValueReceiver} on the
-   * given {@link Executor}.
+   * <p>Results of evaluation will be sent through the given {@link DynamicTypeValueReceiver} on
+   * the given {@link Executor}.
    *
    * @param durationSource The given duration dynamic type that should be evaluated.
    * @param executor The Executor to run the consumer on.
@@ -254,7 +262,8 @@
       @NonNull DynamicBuilders.DynamicDuration durationSource,
       @NonNull Executor executor,
       @NonNull DynamicTypeValueReceiver<Duration> consumer) {
-    return new DynamicDurationBindingRequestWithExecutor(durationSource, executor, consumer);
+    return new DynamicDurationBindingRequest(
+        durationSource.toDynamicDurationProto(), executor, consumer);
   }
 
   /**
@@ -263,12 +272,13 @@
    *
    * @param instantSource The given instant dynamic type that should be evaluated.
    * @param consumer The registered consumer for results of the evaluation. It will be called from
-   *     UI thread.
+   *   UI thread.
    */
   @NonNull
   @RestrictTo(Scope.LIBRARY_GROUP)
   public static DynamicTypeBindingRequest forDynamicInstantInternal(
-      @NonNull DynamicInstant instantSource, @NonNull DynamicTypeValueReceiver<Instant> consumer) {
+      @NonNull DynamicInstant instantSource,
+      @NonNull DynamicTypeValueReceiver<Instant> consumer) {
     return new DynamicInstantBindingRequest(instantSource, consumer);
   }
 
@@ -276,8 +286,8 @@
    * Creates a {@link DynamicTypeBindingRequest} from the given {@link
    * DynamicBuilders.DynamicInstant} for binding.
    *
-   * <p>Results of evaluation will be sent through the given {@link DynamicTypeValueReceiver} on the
-   * given {@link Executor}.
+   * <p>Results of evaluation will be sent through the given {@link DynamicTypeValueReceiver} on
+   * the given {@link Executor}.
    *
    * @param instantSource The given instant dynamic type that should be evaluated.
    * @param executor The Executor to run the consumer on.
@@ -288,142 +298,28 @@
       @NonNull DynamicBuilders.DynamicInstant instantSource,
       @NonNull Executor executor,
       @NonNull DynamicTypeValueReceiver<Instant> consumer) {
-    return new DynamicInstantBindingRequestWithExecutor(instantSource, executor, consumer);
+    return new DynamicInstantBindingRequest(
+        instantSource.toDynamicInstantProto(), executor, consumer);
+  }
+
+  @NonNull
+  private static <T> DynamicTypeValueReceiverOnExecutor<T> createReceiver(
+      @Nullable Executor executor, @NonNull DynamicTypeValueReceiver<T> consumer) {
+    if (executor != null) {
+      return new DynamicTypeValueReceiverOnExecutor<>(executor, consumer);
+    } else {
+      return new DynamicTypeValueReceiverOnExecutor<>(consumer);
+    }
   }
 
   private static class DynamicFloatBindingRequest extends DynamicTypeBindingRequest {
 
     @NonNull private final DynamicFloat mFloatSource;
+    @Nullable private final Executor mExecutor;
     @NonNull private final DynamicTypeValueReceiver<Float> mConsumer;
 
     DynamicFloatBindingRequest(
-        @NonNull DynamicFloat floatSource, @NonNull DynamicTypeValueReceiver<Float> consumer) {
-      mFloatSource = floatSource;
-      mConsumer = consumer;
-    }
-
-    @Override
-    BoundDynamicTypeImpl callBindOn(DynamicTypeEvaluator evaluator) {
-      return evaluator.bindInternal(mFloatSource, mConsumer);
-    }
-  }
-
-  private static class DynamicInt32BindingRequest extends DynamicTypeBindingRequest {
-
-    @NonNull private final DynamicInt32 mInt32Source;
-    @NonNull private final DynamicTypeValueReceiver<Integer> mConsumer;
-
-    DynamicInt32BindingRequest(
-        @NonNull DynamicInt32 int32Source, @NonNull DynamicTypeValueReceiver<Integer> consumer) {
-      mInt32Source = int32Source;
-      mConsumer = consumer;
-    }
-
-    @Override
-    BoundDynamicTypeImpl callBindOn(DynamicTypeEvaluator evaluator) {
-      return evaluator.bindInternal(mInt32Source, mConsumer);
-    }
-  }
-
-  private static class DynamicColorBindingRequest extends DynamicTypeBindingRequest {
-
-    @NonNull private final DynamicColor mColorSource;
-    @NonNull private final DynamicTypeValueReceiver<Integer> mConsumer;
-
-    DynamicColorBindingRequest(
-        @NonNull DynamicColor colorSource, @NonNull DynamicTypeValueReceiver<Integer> consumer) {
-      mColorSource = colorSource;
-      mConsumer = consumer;
-    }
-
-    @Override
-    BoundDynamicTypeImpl callBindOn(DynamicTypeEvaluator evaluator) {
-      return evaluator.bindInternal(mColorSource, mConsumer);
-    }
-  }
-
-  private static class DynamicBoolBindingRequest extends DynamicTypeBindingRequest {
-
-    @NonNull private final DynamicBool mBoolSource;
-    @NonNull private final DynamicTypeValueReceiver<Boolean> mConsumer;
-
-    DynamicBoolBindingRequest(
-        @NonNull DynamicBool boolSource, @NonNull DynamicTypeValueReceiver<Boolean> consumer) {
-      mBoolSource = boolSource;
-      mConsumer = consumer;
-    }
-
-    @Override
-    BoundDynamicTypeImpl callBindOn(DynamicTypeEvaluator evaluator) {
-      return evaluator.bindInternal(mBoolSource, mConsumer);
-    }
-  }
-
-  private static class DynamicStringBindingRequest extends DynamicTypeBindingRequest {
-
-    @NonNull private final DynamicString mStringSource;
-    @NonNull private final ULocale mLocale;
-    @NonNull private final DynamicTypeValueReceiver<String> mConsumer;
-
-    DynamicStringBindingRequest(
-        @NonNull DynamicString stringSource,
-        @NonNull ULocale locale,
-        @NonNull DynamicTypeValueReceiver<String> consumer) {
-      mStringSource = stringSource;
-      mLocale = locale;
-      mConsumer = consumer;
-    }
-
-    @Override
-    BoundDynamicTypeImpl callBindOn(DynamicTypeEvaluator evaluator) {
-      return evaluator.bindInternal(mStringSource, mLocale, mConsumer);
-    }
-  }
-
-  private static class DynamicDurationBindingRequest extends DynamicTypeBindingRequest {
-
-    @NonNull private final DynamicDuration mDurationSource;
-    @NonNull private final DynamicTypeValueReceiver<Duration> mConsumer;
-
-    DynamicDurationBindingRequest(
-        @NonNull DynamicDuration durationSource,
-        @NonNull DynamicTypeValueReceiver<Duration> consumer) {
-      mDurationSource = durationSource;
-      mConsumer = consumer;
-    }
-
-    @Override
-    BoundDynamicTypeImpl callBindOn(DynamicTypeEvaluator evaluator) {
-      return evaluator.bindInternal(mDurationSource, mConsumer);
-    }
-  }
-
-  private static class DynamicInstantBindingRequest extends DynamicTypeBindingRequest {
-
-    @NonNull private final DynamicInstant mInstantSource;
-    @NonNull private final DynamicTypeValueReceiver<Instant> mConsumer;
-
-    DynamicInstantBindingRequest(
-        @NonNull DynamicInstant instantSource,
-        @NonNull DynamicTypeValueReceiver<Instant> consumer) {
-      mInstantSource = instantSource;
-      mConsumer = consumer;
-    }
-
-    @Override
-    BoundDynamicTypeImpl callBindOn(DynamicTypeEvaluator evaluator) {
-      return evaluator.bindInternal(mInstantSource, mConsumer);
-    }
-  }
-
-  private static class DynamicFloatBindingRequestWithExecutor extends DynamicTypeBindingRequest {
-
-    @NonNull private final DynamicBuilders.DynamicFloat mFloatSource;
-    @NonNull private final Executor mExecutor;
-    @NonNull private final DynamicTypeValueReceiver<Float> mConsumer;
-
-    DynamicFloatBindingRequestWithExecutor(
-        @NonNull DynamicBuilders.DynamicFloat floatSource,
+        @NonNull DynamicFloat floatSource,
         @NonNull Executor executor,
         @NonNull DynamicTypeValueReceiver<Float> consumer) {
       mFloatSource = floatSource;
@@ -431,20 +327,28 @@
       mConsumer = consumer;
     }
 
+    DynamicFloatBindingRequest(
+        @NonNull DynamicFloat floatSource,
+        @NonNull DynamicTypeValueReceiver<Float> consumer) {
+      mFloatSource = floatSource;
+      mConsumer = consumer;
+      mExecutor = null;
+    }
+
     @Override
     BoundDynamicTypeImpl callBindOn(DynamicTypeEvaluator evaluator) {
-      return evaluator.bindInternal(mFloatSource, mExecutor, mConsumer);
+      return evaluator.bindInternal(mFloatSource, createReceiver(mExecutor, mConsumer));
     }
   }
 
-  private static class DynamicInt32BindingRequestWithExecutor extends DynamicTypeBindingRequest {
+  private static class DynamicInt32BindingRequest extends DynamicTypeBindingRequest {
 
-    @NonNull private final DynamicBuilders.DynamicInt32 mInt32Source;
-    @NonNull private final Executor mExecutor;
+    @NonNull private final DynamicInt32 mInt32Source;
+    @Nullable private final Executor mExecutor;
     @NonNull private final DynamicTypeValueReceiver<Integer> mConsumer;
 
-    DynamicInt32BindingRequestWithExecutor(
-        @NonNull DynamicBuilders.DynamicInt32 int32Source,
+    DynamicInt32BindingRequest(
+        @NonNull DynamicInt32 int32Source,
         @NonNull Executor executor,
         @NonNull DynamicTypeValueReceiver<Integer> consumer) {
       mInt32Source = int32Source;
@@ -452,20 +356,28 @@
       mConsumer = consumer;
     }
 
+    DynamicInt32BindingRequest(
+        @NonNull DynamicInt32 int32Source,
+        @NonNull DynamicTypeValueReceiver<Integer> consumer) {
+      mInt32Source = int32Source;
+      mConsumer = consumer;
+      mExecutor = null;
+    }
+
     @Override
     BoundDynamicTypeImpl callBindOn(DynamicTypeEvaluator evaluator) {
-      return evaluator.bindInternal(mInt32Source, mExecutor, mConsumer);
+      return evaluator.bindInternal(mInt32Source, createReceiver(mExecutor, mConsumer));
     }
   }
 
-  private static class DynamicColorBindingRequestWithExecutor extends DynamicTypeBindingRequest {
+  private static class DynamicColorBindingRequest extends DynamicTypeBindingRequest {
 
-    @NonNull private final DynamicBuilders.DynamicColor mColorSource;
-    @NonNull private final Executor mExecutor;
+    @NonNull private final DynamicColor mColorSource;
+    @Nullable private final Executor mExecutor;
     @NonNull private final DynamicTypeValueReceiver<Integer> mConsumer;
 
-    DynamicColorBindingRequestWithExecutor(
-        @NonNull DynamicBuilders.DynamicColor colorSource,
+    DynamicColorBindingRequest(
+        @NonNull DynamicColor colorSource,
         @NonNull Executor executor,
         @NonNull DynamicTypeValueReceiver<Integer> consumer) {
       mColorSource = colorSource;
@@ -473,20 +385,28 @@
       mConsumer = consumer;
     }
 
+    DynamicColorBindingRequest(
+        @NonNull DynamicColor colorSource,
+        @NonNull DynamicTypeValueReceiver<Integer> consumer) {
+      mColorSource = colorSource;
+      mConsumer = consumer;
+      mExecutor = null;
+    }
+
     @Override
     BoundDynamicTypeImpl callBindOn(DynamicTypeEvaluator evaluator) {
-      return evaluator.bindInternal(mColorSource, mExecutor, mConsumer);
+      return evaluator.bindInternal(mColorSource, createReceiver(mExecutor, mConsumer));
     }
   }
 
-  private static class DynamicBoolBindingRequestWithExecutor extends DynamicTypeBindingRequest {
+  private static class DynamicBoolBindingRequest extends DynamicTypeBindingRequest {
 
-    @NonNull private final DynamicBuilders.DynamicBool mBoolSource;
-    @NonNull private final Executor mExecutor;
+    @NonNull private final DynamicBool mBoolSource;
+    @Nullable private final Executor mExecutor;
     @NonNull private final DynamicTypeValueReceiver<Boolean> mConsumer;
 
-    DynamicBoolBindingRequestWithExecutor(
-        @NonNull DynamicBuilders.DynamicBool boolSource,
+    DynamicBoolBindingRequest(
+        @NonNull DynamicBool boolSource,
         @NonNull Executor executor,
         @NonNull DynamicTypeValueReceiver<Boolean> consumer) {
       mBoolSource = boolSource;
@@ -494,21 +414,29 @@
       mConsumer = consumer;
     }
 
+    DynamicBoolBindingRequest(
+        @NonNull DynamicBool boolSource,
+        @NonNull DynamicTypeValueReceiver<Boolean> consumer) {
+      mBoolSource = boolSource;
+      mConsumer = consumer;
+      mExecutor = null;
+    }
+
     @Override
     BoundDynamicTypeImpl callBindOn(DynamicTypeEvaluator evaluator) {
-      return evaluator.bindInternal(mBoolSource, mExecutor, mConsumer);
+      return evaluator.bindInternal(mBoolSource, createReceiver(mExecutor, mConsumer));
     }
   }
 
-  private static class DynamicStringBindingRequestWithExecutor extends DynamicTypeBindingRequest {
+  private static class DynamicStringBindingRequest extends DynamicTypeBindingRequest {
 
-    @NonNull private final DynamicBuilders.DynamicString mStringSource;
+    @NonNull private final DynamicString mStringSource;
     @NonNull private final ULocale mLocale;
-    @NonNull private final Executor mExecutor;
+    @Nullable private final Executor mExecutor;
     @NonNull private final DynamicTypeValueReceiver<String> mConsumer;
 
-    DynamicStringBindingRequestWithExecutor(
-        @NonNull DynamicBuilders.DynamicString stringSource,
+    DynamicStringBindingRequest(
+        @NonNull DynamicString stringSource,
         @NonNull ULocale locale,
         @NonNull Executor executor,
         @NonNull DynamicTypeValueReceiver<String> consumer) {
@@ -518,20 +446,31 @@
       this.mLocale = locale;
     }
 
+    DynamicStringBindingRequest(
+        @NonNull DynamicString stringSource,
+        @NonNull ULocale locale,
+        @NonNull DynamicTypeValueReceiver<String> consumer) {
+      mStringSource = stringSource;
+      mConsumer = consumer;
+      mLocale = locale;
+      mExecutor = null;
+    }
+
     @Override
     BoundDynamicTypeImpl callBindOn(DynamicTypeEvaluator evaluator) {
-      return evaluator.bindInternal(mStringSource, mLocale, mExecutor, mConsumer);
+      return evaluator.bindInternal(
+          mStringSource, mLocale, createReceiver(mExecutor, mConsumer));
     }
   }
 
-  private static class DynamicDurationBindingRequestWithExecutor extends DynamicTypeBindingRequest {
+  private static class DynamicDurationBindingRequest extends DynamicTypeBindingRequest {
 
-    @NonNull private final DynamicBuilders.DynamicDuration mDurationSource;
-    @NonNull private final Executor mExecutor;
+    @NonNull private final DynamicDuration mDurationSource;
+    @Nullable private final Executor mExecutor;
     @NonNull private final DynamicTypeValueReceiver<Duration> mConsumer;
 
-    DynamicDurationBindingRequestWithExecutor(
-        @NonNull DynamicBuilders.DynamicDuration durationSource,
+    DynamicDurationBindingRequest(
+        @NonNull DynamicDuration durationSource,
         @NonNull Executor executor,
         @NonNull DynamicTypeValueReceiver<Duration> consumer) {
       mDurationSource = durationSource;
@@ -539,20 +478,28 @@
       mConsumer = consumer;
     }
 
+    DynamicDurationBindingRequest(
+        @NonNull DynamicDuration durationSource,
+        @NonNull DynamicTypeValueReceiver<Duration> consumer) {
+      mDurationSource = durationSource;
+      mConsumer = consumer;
+      mExecutor = null;
+    }
+
     @Override
     BoundDynamicTypeImpl callBindOn(DynamicTypeEvaluator evaluator) {
-      return evaluator.bindInternal(mDurationSource, mExecutor, mConsumer);
+      return evaluator.bindInternal(mDurationSource, createReceiver(mExecutor, mConsumer));
     }
   }
 
-  private static class DynamicInstantBindingRequestWithExecutor extends DynamicTypeBindingRequest {
+  private static class DynamicInstantBindingRequest extends DynamicTypeBindingRequest {
 
-    @NonNull private final DynamicBuilders.DynamicInstant mInstantSource;
-    @NonNull private final Executor mExecutor;
+    @NonNull private final DynamicInstant mInstantSource;
+    @Nullable private final Executor mExecutor;
     @NonNull private final DynamicTypeValueReceiver<Instant> mConsumer;
 
-    DynamicInstantBindingRequestWithExecutor(
-        @NonNull DynamicBuilders.DynamicInstant instantSource,
+    DynamicInstantBindingRequest(
+        @NonNull DynamicInstant instantSource,
         @NonNull Executor executor,
         @NonNull DynamicTypeValueReceiver<Instant> consumer) {
       mInstantSource = instantSource;
@@ -560,9 +507,55 @@
       mConsumer = consumer;
     }
 
+    DynamicInstantBindingRequest(
+        @NonNull DynamicInstant instantSource,
+        @NonNull DynamicTypeValueReceiver<Instant> consumer) {
+      mInstantSource = instantSource;
+      mConsumer = consumer;
+      mExecutor = null;
+    }
+
     @Override
     BoundDynamicTypeImpl callBindOn(DynamicTypeEvaluator evaluator) {
-      return evaluator.bindInternal(mInstantSource, mExecutor, mConsumer);
+      return evaluator.bindInternal(mInstantSource, createReceiver(mExecutor, mConsumer));
+    }
+  }
+
+  /**
+   * Wraps {@link DynamicTypeValueReceiver} and executes its methods on the given {@link
+   * Executor}.
+   */
+  private static class DynamicTypeValueReceiverOnExecutor<T>
+      implements DynamicTypeValueReceiverWithPreUpdate<T> {
+
+    @NonNull private final Executor mExecutor;
+    @NonNull private final DynamicTypeValueReceiver<T> mConsumer;
+
+    DynamicTypeValueReceiverOnExecutor(@NonNull DynamicTypeValueReceiver<T> consumer) {
+      this(Runnable::run, consumer);
+    }
+
+    DynamicTypeValueReceiverOnExecutor(
+        @NonNull Executor executor, @NonNull DynamicTypeValueReceiver<T> consumer) {
+      this.mConsumer = consumer;
+      this.mExecutor = executor;
+    }
+
+    /** This method is noop in this class. */
+    @Override
+    @SuppressWarnings("ExecutorTaskName")
+    public void onPreUpdate() {}
+
+    @Override
+    @SuppressWarnings("ExecutorTaskName")
+    public void onData(@NonNull T newData) {
+      mExecutor.execute(() -> mConsumer.onData(newData));
+    }
+
+    @Override
+    @SuppressWarnings("ExecutorTaskName")
+    public void onInvalidated() {
+      mExecutor.execute(mConsumer::onInvalidated);
     }
   }
 }
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluator.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluator.java
index 5dc4a67..5e49966 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluator.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluator.java
@@ -27,7 +27,6 @@
 import androidx.annotation.RestrictTo.Scope;
 import androidx.annotation.VisibleForTesting;
 import androidx.collection.ArrayMap;
-import androidx.wear.protolayout.expression.DynamicBuilders;
 import androidx.wear.protolayout.expression.PlatformDataKey;
 import androidx.wear.protolayout.expression.pipeline.BoolNodes.ComparisonFloatNode;
 import androidx.wear.protolayout.expression.pipeline.BoolNodes.ComparisonInt32Node;
@@ -92,7 +91,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.Executor;
 import java.util.function.Supplier;
 
 /**
@@ -392,133 +390,63 @@
     }
 
     @NonNull
-    BoundDynamicTypeImpl bindInternal(
-            @NonNull DynamicBuilders.DynamicString stringSource,
-            @NonNull ULocale locale,
-            @NonNull Executor executor,
-            @NonNull DynamicTypeValueReceiver<String> consumer) {
-        return bindInternal(
-                stringSource.toDynamicStringProto(),
-                locale,
-                new DynamicTypeValueReceiverOnExecutor<>(executor, consumer));
-    }
-
-    @NonNull
     @RestrictTo(Scope.LIBRARY_GROUP)
     BoundDynamicTypeImpl bindInternal(
             @NonNull DynamicString stringSource,
             @NonNull ULocale locale,
-            @NonNull DynamicTypeValueReceiver<String> consumer) {
+            @NonNull DynamicTypeValueReceiverWithPreUpdate<String> consumer) {
         List<DynamicDataNode<?>> resultBuilder = new ArrayList<>();
-        bindRecursively(
-                stringSource,
-                new DynamicTypeValueReceiverOnExecutor<>(consumer),
-                locale,
-                resultBuilder);
+        bindRecursively(stringSource, consumer, locale, resultBuilder);
         return new BoundDynamicTypeImpl(resultBuilder, mDynamicTypesQuotaManager);
     }
 
     @NonNull
-    BoundDynamicTypeImpl bindInternal(
-            @NonNull DynamicBuilders.DynamicInt32 int32Source,
-            @NonNull Executor executor,
-            @NonNull DynamicTypeValueReceiver<Integer> consumer) {
-        return bindInternal(
-                int32Source.toDynamicInt32Proto(),
-                new DynamicTypeValueReceiverOnExecutor<>(executor, consumer));
-    }
-
-    @NonNull
     @RestrictTo(Scope.LIBRARY_GROUP)
     BoundDynamicTypeImpl bindInternal(
             @NonNull DynamicInt32 int32Source,
-            @NonNull DynamicTypeValueReceiver<Integer> consumer) {
+            @NonNull DynamicTypeValueReceiverWithPreUpdate<Integer> consumer) {
         List<DynamicDataNode<?>> resultBuilder = new ArrayList<>();
-        bindRecursively(
-                int32Source, new DynamicTypeValueReceiverOnExecutor<>(consumer), resultBuilder);
+        bindRecursively(int32Source, consumer, resultBuilder);
         return new BoundDynamicTypeImpl(resultBuilder, mDynamicTypesQuotaManager);
     }
 
     @NonNull
-    BoundDynamicTypeImpl bindInternal(
-            @NonNull DynamicBuilders.DynamicFloat floatSource,
-            @NonNull Executor executor,
-            @NonNull DynamicTypeValueReceiver<Float> consumer) {
-        return bindInternal(
-                floatSource.toDynamicFloatProto(),
-                new DynamicTypeValueReceiverOnExecutor<>(executor, consumer));
-    }
-
-    @NonNull
     @RestrictTo(Scope.LIBRARY_GROUP)
     BoundDynamicTypeImpl bindInternal(
-            @NonNull DynamicFloat floatSource, @NonNull DynamicTypeValueReceiver<Float> consumer) {
+            @NonNull DynamicFloat floatSource,
+            @NonNull DynamicTypeValueReceiverWithPreUpdate<Float> consumer) {
         List<DynamicDataNode<?>> resultBuilder = new ArrayList<>();
-        bindRecursively(
-                floatSource, new DynamicTypeValueReceiverOnExecutor<>(consumer), resultBuilder);
+        bindRecursively(floatSource, consumer, resultBuilder);
         return new BoundDynamicTypeImpl(resultBuilder, mDynamicTypesQuotaManager);
     }
 
     @NonNull
-    BoundDynamicTypeImpl bindInternal(
-            @NonNull DynamicBuilders.DynamicColor colorSource,
-            @NonNull Executor executor,
-            @NonNull DynamicTypeValueReceiver<Integer> consumer) {
-        return bindInternal(
-                colorSource.toDynamicColorProto(),
-                new DynamicTypeValueReceiverOnExecutor<>(executor, consumer));
-    }
-
-    @NonNull
     @RestrictTo(Scope.LIBRARY_GROUP)
     BoundDynamicTypeImpl bindInternal(
             @NonNull DynamicColor colorSource,
-            @NonNull DynamicTypeValueReceiver<Integer> consumer) {
+            @NonNull DynamicTypeValueReceiverWithPreUpdate<Integer> consumer) {
         List<DynamicDataNode<?>> resultBuilder = new ArrayList<>();
-        bindRecursively(
-                colorSource, new DynamicTypeValueReceiverOnExecutor<>(consumer), resultBuilder);
+        bindRecursively(colorSource, consumer, resultBuilder);
         return new BoundDynamicTypeImpl(resultBuilder, mDynamicTypesQuotaManager);
     }
 
     @NonNull
-    BoundDynamicTypeImpl bindInternal(
-            @NonNull DynamicBuilders.DynamicDuration durationSource,
-            @NonNull Executor executor,
-            @NonNull DynamicTypeValueReceiver<Duration> consumer) {
-        return bindInternal(
-                durationSource.toDynamicDurationProto(),
-                new DynamicTypeValueReceiverOnExecutor<>(executor, consumer));
-    }
-
-    @NonNull
     @RestrictTo(Scope.LIBRARY_GROUP)
     BoundDynamicTypeImpl bindInternal(
             @NonNull DynamicDuration durationSource,
-            @NonNull DynamicTypeValueReceiver<Duration> consumer) {
+            @NonNull DynamicTypeValueReceiverWithPreUpdate<Duration> consumer) {
         List<DynamicDataNode<?>> resultBuilder = new ArrayList<>();
-        bindRecursively(
-                durationSource, new DynamicTypeValueReceiverOnExecutor<>(consumer), resultBuilder);
+        bindRecursively(durationSource, consumer, resultBuilder);
         return new BoundDynamicTypeImpl(resultBuilder, mDynamicTypesQuotaManager);
     }
 
     @NonNull
-    BoundDynamicTypeImpl bindInternal(
-            @NonNull DynamicBuilders.DynamicInstant instantSource,
-            @NonNull Executor executor,
-            @NonNull DynamicTypeValueReceiver<Instant> consumer) {
-        return bindInternal(
-                instantSource.toDynamicInstantProto(),
-                new DynamicTypeValueReceiverOnExecutor<>(executor, consumer));
-    }
-
-    @NonNull
     @RestrictTo(Scope.LIBRARY_GROUP)
     BoundDynamicTypeImpl bindInternal(
             @NonNull DynamicInstant instantSource,
-            @NonNull DynamicTypeValueReceiver<Instant> consumer) {
+            @NonNull DynamicTypeValueReceiverWithPreUpdate<Instant> consumer) {
         List<DynamicDataNode<?>> resultBuilder = new ArrayList<>();
-        bindRecursively(
-                instantSource, new DynamicTypeValueReceiverOnExecutor<>(consumer), resultBuilder);
+        bindRecursively(instantSource, consumer, resultBuilder);
         return new BoundDynamicTypeImpl(resultBuilder, mDynamicTypesQuotaManager);
     }
 
@@ -526,30 +454,19 @@
     @RestrictTo(Scope.LIBRARY_GROUP)
     BoundDynamicTypeImpl bindInternal(
             @NonNull DynamicZonedDateTime zdtSource,
-            @NonNull DynamicTypeValueReceiver<ZonedDateTime> consumer) {
+            @NonNull DynamicTypeValueReceiverWithPreUpdate<ZonedDateTime> consumer) {
         List<DynamicDataNode<?>> resultBuilder = new ArrayList<>();
-        bindRecursively(
-                zdtSource, new DynamicTypeValueReceiverOnExecutor<>(consumer), resultBuilder);
+        bindRecursively(zdtSource, consumer, resultBuilder);
         return new BoundDynamicTypeImpl(resultBuilder, mDynamicTypesQuotaManager);
     }
 
     @NonNull
-    BoundDynamicTypeImpl bindInternal(
-            @NonNull DynamicBuilders.DynamicBool boolSource,
-            @NonNull Executor executor,
-            @NonNull DynamicTypeValueReceiver<Boolean> consumer) {
-        return bindInternal(
-                boolSource.toDynamicBoolProto(),
-                new DynamicTypeValueReceiverOnExecutor<>(executor, consumer));
-    }
-
-    @NonNull
     @RestrictTo(Scope.LIBRARY_GROUP)
     BoundDynamicTypeImpl bindInternal(
-            @NonNull DynamicBool boolSource, @NonNull DynamicTypeValueReceiver<Boolean> consumer) {
+            @NonNull DynamicBool boolSource,
+            @NonNull DynamicTypeValueReceiverWithPreUpdate<Boolean> consumer) {
         List<DynamicDataNode<?>> resultBuilder = new ArrayList<>();
-        bindRecursively(
-                boolSource, new DynamicTypeValueReceiverOnExecutor<>(consumer), resultBuilder);
+        bindRecursively(boolSource, consumer, resultBuilder);
         return new BoundDynamicTypeImpl(resultBuilder, mDynamicTypesQuotaManager);
     }
 
@@ -1228,42 +1145,4 @@
 
         resultBuilder.add(node);
     }
-
-    /**
-     * Wraps {@link DynamicTypeValueReceiver} and executes its methods on the given {@link
-     * Executor}.
-     */
-    private static class DynamicTypeValueReceiverOnExecutor<T>
-            implements DynamicTypeValueReceiverWithPreUpdate<T> {
-
-        @NonNull private final Executor mExecutor;
-        @NonNull private final DynamicTypeValueReceiver<T> mConsumer;
-
-        DynamicTypeValueReceiverOnExecutor(@NonNull DynamicTypeValueReceiver<T> consumer) {
-            this(Runnable::run, consumer);
-        }
-
-        DynamicTypeValueReceiverOnExecutor(
-                @NonNull Executor executor, @NonNull DynamicTypeValueReceiver<T> consumer) {
-            this.mConsumer = consumer;
-            this.mExecutor = executor;
-        }
-
-        /** This method is noop in this class. */
-        @Override
-        @SuppressWarnings("ExecutorTaskName")
-        public void onPreUpdate() {}
-
-        @Override
-        @SuppressWarnings("ExecutorTaskName")
-        public void onData(@NonNull T newData) {
-            mExecutor.execute(() -> mConsumer.onData(newData));
-        }
-
-        @Override
-        @SuppressWarnings("ExecutorTaskName")
-        public void onInvalidated() {
-            mExecutor.execute(mConsumer::onInvalidated);
-        }
-    }
 }
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/FloatNodes.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/FloatNodes.java
index fb45b99..77a8fb1 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/FloatNodes.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/FloatNodes.java
@@ -96,13 +96,9 @@
         }
 
         private static float computeResult(
-                DynamicProto.ArithmeticOpType opType, Float lhs, Float rhs) {
+                DynamicProto.ArithmeticOpType opType, float lhs, float rhs) {
             try {
                 switch (opType) {
-                    case ARITHMETIC_OP_TYPE_UNDEFINED:
-                    case UNRECOGNIZED:
-                        Log.e(TAG, "Unknown operation type in ArithmeticFloatNode");
-                        return Float.NaN;
                     case ARITHMETIC_OP_TYPE_ADD:
                         return lhs + rhs;
                     case ARITHMETIC_OP_TYPE_SUBTRACT:
@@ -113,13 +109,16 @@
                         return lhs / rhs;
                     case ARITHMETIC_OP_TYPE_MODULO:
                         return lhs % rhs;
+                    case ARITHMETIC_OP_TYPE_UNDEFINED:
+                    case UNRECOGNIZED:
+                        break;
                 }
             } catch (ArithmeticException ex) {
                 Log.e(TAG, "ArithmeticException in ArithmeticFloatNode", ex);
                 return Float.NaN;
             }
-            Log.e(TAG, "Unknown operation type in ArithmeticFloatNode");
-            return Float.NaN;
+            throw new IllegalArgumentException(
+                    "Unknown operation type in ArithmeticFloatNode: " + opType);
         }
     }
 
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/Int32Nodes.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/Int32Nodes.java
index b43d388..6aca826 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/Int32Nodes.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/Int32Nodes.java
@@ -133,10 +133,6 @@
                     (lhs, rhs) -> {
                         try {
                             switch (protoNode.getOperationType()) {
-                                case ARITHMETIC_OP_TYPE_UNDEFINED:
-                                case UNRECOGNIZED:
-                                    Log.e(TAG, "Unknown operation type in ArithmeticInt32Node");
-                                    return 0;
                                 case ARITHMETIC_OP_TYPE_ADD:
                                     return lhs + rhs;
                                 case ARITHMETIC_OP_TYPE_SUBTRACT:
@@ -147,14 +143,18 @@
                                     return lhs / rhs;
                                 case ARITHMETIC_OP_TYPE_MODULO:
                                     return lhs % rhs;
+                                case ARITHMETIC_OP_TYPE_UNDEFINED:
+                                case UNRECOGNIZED:
+                                    break;
                             }
                         } catch (ArithmeticException ex) {
                             Log.e(TAG, "ArithmeticException in ArithmeticInt32Node", ex);
                             return null;
                         }
 
-                        Log.e(TAG, "Unknown operation type in ArithmeticInt32Node");
-                        return 0;
+                        throw new IllegalArgumentException(
+                                "Unknown operation type in ArithmeticInt32Node: "
+                                        + protoNode.getOperationType());
                     });
         }
     }
@@ -185,16 +185,18 @@
                     downstream,
                     x -> {
                         switch (protoNode.getRoundMode()) {
-                            case ROUND_MODE_UNDEFINED:
                             case ROUND_MODE_FLOOR:
                                 return (int) Math.floor(x);
                             case ROUND_MODE_ROUND:
                                 return Math.round(x);
                             case ROUND_MODE_CEILING:
                                 return (int) Math.ceil(x);
-                            default:
-                                throw new IllegalArgumentException("Unknown rounding mode");
+                            case ROUND_MODE_UNDEFINED:
+                            case UNRECOGNIZED:
+                                break;
                         }
+                        throw new IllegalArgumentException(
+                                "Unknown rounding mode:" + protoNode.getRoundMode());
                     },
                     x -> x - 1 < Integer.MAX_VALUE && x >= Integer.MIN_VALUE);
         }
@@ -214,10 +216,6 @@
 
         private static long getDurationPart(Duration duration, DurationPartType durationPartType) {
             switch (durationPartType) {
-                case DURATION_PART_TYPE_UNDEFINED:
-                case UNRECOGNIZED:
-                    Log.e(TAG, "Unknown duration part type in GetDurationPartOpNode");
-                    return 0;
                 case DURATION_PART_TYPE_DAYS:
                     return abs(duration.getSeconds() / (3600 * 24));
                 case DURATION_PART_TYPE_HOURS:
@@ -234,8 +232,11 @@
                     return duration.toMinutes();
                 case DURATION_PART_TYPE_TOTAL_SECONDS:
                     return duration.getSeconds();
+                case DURATION_PART_TYPE_UNDEFINED:
+                case UNRECOGNIZED:
+                    break;
             }
-            throw new IllegalArgumentException("Unknown duration part");
+            throw new IllegalArgumentException("Unknown duration part: " + durationPartType);
         }
     }
 
@@ -363,10 +364,6 @@
 
         private static long getZonedDateTimePart(ZonedDateTime zdt, ZonedDateTimePartType type) {
             switch (type) {
-                case ZONED_DATE_TIME_PART_UNDEFINED:
-                case UNRECOGNIZED:
-                    Log.e(TAG, "Unknown ZonedDateTime part.");
-                    return 0;
                 case ZONED_DATE_TIME_PART_SECOND:
                     return zdt.getSecond();
                 case ZONED_DATE_TIME_PART_MINUTE:
@@ -381,8 +378,11 @@
                     return zdt.getMonth().getValue();
                 case ZONED_DATE_TIME_PART_YEAR:
                     return zdt.getYear();
+                case ZONED_DATE_TIME_PART_UNDEFINED:
+                case UNRECOGNIZED:
+                    break;
             }
-            throw new IllegalArgumentException("Unknown ZonedDateTime part.");
+            throw new IllegalArgumentException("Unknown ZonedDateTime part: " + type);
         }
     }
 }
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/BoolNodesTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/BoolNodesTest.java
index 176d5bc..7a67bd8e 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/BoolNodesTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/BoolNodesTest.java
@@ -16,23 +16,37 @@
 
 package androidx.wear.protolayout.expression.pipeline;
 
+import static androidx.wear.protolayout.expression.proto.DynamicProto.ComparisonOpType.COMPARISON_OP_TYPE_EQUALS;
+import static androidx.wear.protolayout.expression.proto.DynamicProto.ComparisonOpType.COMPARISON_OP_TYPE_GREATER_THAN;
+import static androidx.wear.protolayout.expression.proto.DynamicProto.ComparisonOpType.COMPARISON_OP_TYPE_GREATER_THAN_OR_EQUAL_TO;
+import static androidx.wear.protolayout.expression.proto.DynamicProto.ComparisonOpType.COMPARISON_OP_TYPE_LESS_THAN;
+import static androidx.wear.protolayout.expression.proto.DynamicProto.ComparisonOpType.COMPARISON_OP_TYPE_LESS_THAN_OR_EQUAL_TO;
+import static androidx.wear.protolayout.expression.proto.DynamicProto.ComparisonOpType.COMPARISON_OP_TYPE_NOT_EQUALS;
+import static androidx.wear.protolayout.expression.proto.DynamicProto.ComparisonOpType.COMPARISON_OP_TYPE_UNDEFINED;
 import static androidx.wear.protolayout.expression.proto.DynamicProto.LogicalOpType.LOGICAL_OP_TYPE_AND;
 import static androidx.wear.protolayout.expression.proto.DynamicProto.LogicalOpType.LOGICAL_OP_TYPE_EQUAL;
 import static androidx.wear.protolayout.expression.proto.DynamicProto.LogicalOpType.LOGICAL_OP_TYPE_NOT_EQUAL;
 import static androidx.wear.protolayout.expression.proto.DynamicProto.LogicalOpType.LOGICAL_OP_TYPE_OR;
+import static androidx.wear.protolayout.expression.proto.DynamicProto.LogicalOpType.LOGICAL_OP_TYPE_UNDEFINED;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
+
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.wear.protolayout.expression.AppDataKey;
 import androidx.wear.protolayout.expression.DynamicBuilders.DynamicBool;
 import androidx.wear.protolayout.expression.pipeline.BoolNodes.FixedBoolNode;
 import androidx.wear.protolayout.expression.pipeline.BoolNodes.StateBoolNode;
+import androidx.wear.protolayout.expression.pipeline.FloatNodes.FixedFloatNode;
+import androidx.wear.protolayout.expression.pipeline.Int32Nodes.FixedInt32Node;
 import androidx.wear.protolayout.expression.proto.DynamicDataProto.DynamicDataValue;
 import androidx.wear.protolayout.expression.proto.DynamicProto;
 import androidx.wear.protolayout.expression.proto.DynamicProto.LogicalBoolOp;
 import androidx.wear.protolayout.expression.proto.DynamicProto.StateBoolSource;
 import androidx.wear.protolayout.expression.proto.FixedProto.FixedBool;
+import androidx.wear.protolayout.expression.proto.FixedProto.FixedFloat;
+import androidx.wear.protolayout.expression.proto.FixedProto.FixedInt32;
 
 import com.google.common.collect.ImmutableMap;
 
@@ -44,129 +58,302 @@
 
 @RunWith(AndroidJUnit4.class)
 public class BoolNodesTest {
-  @Test
-  public void fixedBoolNodeTest() {
-    List<Boolean> results = new ArrayList<>();
+    @Test
+    public void fixedBoolNodeTest() {
+        List<Boolean> results = new ArrayList<>();
 
-    FixedBool protoNode = FixedBool.newBuilder().setValue(false).build();
-    FixedBoolNode node = new FixedBoolNode(protoNode, new AddToListCallback<>(results));
+        FixedBool protoNode = FixedBool.newBuilder().setValue(false).build();
+        FixedBoolNode node = new FixedBoolNode(protoNode, new AddToListCallback<>(results));
 
-    node.preInit();
-    node.init();
+        node.preInit();
+        node.init();
 
-    assertThat(results).containsExactly(false);
-  }
+        assertThat(results).containsExactly(false);
+    }
 
-  @Test
-  public void stateBoolNodeTest() {
-    List<Boolean> results = new ArrayList<>();
-    StateStore oss =
-        new StateStore(
-            ImmutableMap.of(
-                new AppDataKey<DynamicBool>("foo"),
-                DynamicDataValue.newBuilder()
-                    .setBoolVal(FixedBool.newBuilder().setValue(true))
-                    .build()));
+    @Test
+    public void stateBoolNodeTest() {
+        List<Boolean> results = new ArrayList<>();
+        StateStore oss =
+                new StateStore(
+                        ImmutableMap.of(
+                                new AppDataKey<DynamicBool>("foo"),
+                                DynamicDataValue.newBuilder()
+                                        .setBoolVal(FixedBool.newBuilder().setValue(true))
+                                        .build()));
 
-    StateBoolSource protoNode = StateBoolSource.newBuilder().setSourceKey("foo").build();
-    StateBoolNode node = new StateBoolNode(oss, protoNode, new AddToListCallback<>(results));
+        StateBoolSource protoNode = StateBoolSource.newBuilder().setSourceKey("foo").build();
+        StateBoolNode node = new StateBoolNode(oss, protoNode, new AddToListCallback<>(results));
 
-    node.preInit();
-    node.init();
+        node.preInit();
+        node.init();
 
-    assertThat(results).containsExactly(true);
-  }
+        assertThat(results).containsExactly(true);
+    }
 
-  @Test
-  public void stateBoolUpdatesWithStateChanges() {
-    List<Boolean> results = new ArrayList<>();
-    StateStore oss =
-        new StateStore(
-            ImmutableMap.of(
-                new AppDataKey<DynamicBool>("foo"),
-                DynamicDataValue.newBuilder()
-                    .setBoolVal(FixedBool.newBuilder().setValue(true))
-                    .build()));
+    @Test
+    public void stateBoolUpdatesWithStateChanges() {
+        List<Boolean> results = new ArrayList<>();
+        StateStore oss =
+                new StateStore(
+                        ImmutableMap.of(
+                                new AppDataKey<DynamicBool>("foo"),
+                                DynamicDataValue.newBuilder()
+                                        .setBoolVal(FixedBool.newBuilder().setValue(true))
+                                        .build()));
 
-    StateBoolSource protoNode = StateBoolSource.newBuilder().setSourceKey("foo").build();
-    StateBoolNode node = new StateBoolNode(oss, protoNode, new AddToListCallback<>(results));
+        StateBoolSource protoNode = StateBoolSource.newBuilder().setSourceKey("foo").build();
+        StateBoolNode node = new StateBoolNode(oss, protoNode, new AddToListCallback<>(results));
 
-    node.preInit();
-    node.init();
+        node.preInit();
+        node.init();
 
-    results.clear();
+        results.clear();
 
-    oss.setAppStateEntryValuesProto(
-        ImmutableMap.of(
-            new AppDataKey<DynamicBool>("foo"),
-            DynamicDataValue.newBuilder()
-                .setBoolVal(FixedBool.newBuilder().setValue(false))
-                .build()));
+        oss.setAppStateEntryValuesProto(
+                ImmutableMap.of(
+                        new AppDataKey<DynamicBool>("foo"),
+                        DynamicDataValue.newBuilder()
+                                .setBoolVal(FixedBool.newBuilder().setValue(false))
+                                .build()));
 
-    assertThat(results).containsExactly(false);
-  }
+        assertThat(results).containsExactly(false);
+    }
 
-  @Test
-  public void stateBoolNoUpdatesAfterDestroy() {
-    List<Boolean> results = new ArrayList<>();
-    AppDataKey<DynamicBool> keyFoo = new AppDataKey<>("foo");
-    StateStore oss =
-        new StateStore(
-            ImmutableMap.of(
-                keyFoo,
-                DynamicDataValue.newBuilder()
-                    .setBoolVal(FixedBool.newBuilder().setValue(false))
-                    .build()));
+    @Test
+    public void stateBoolNoUpdatesAfterDestroy() {
+        List<Boolean> results = new ArrayList<>();
+        AppDataKey<DynamicBool> keyFoo = new AppDataKey<>("foo");
+        StateStore oss =
+                new StateStore(
+                        ImmutableMap.of(
+                                keyFoo,
+                                DynamicDataValue.newBuilder()
+                                        .setBoolVal(FixedBool.newBuilder().setValue(false))
+                                        .build()));
 
-    StateBoolSource protoNode = StateBoolSource.newBuilder().setSourceKey("foo").build();
-    StateBoolNode node = new StateBoolNode(oss, protoNode, new AddToListCallback<>(results));
+        StateBoolSource protoNode = StateBoolSource.newBuilder().setSourceKey("foo").build();
+        StateBoolNode node = new StateBoolNode(oss, protoNode, new AddToListCallback<>(results));
 
-    node.preInit();
-    node.init();
-    assertThat(results).containsExactly(false);
+        node.preInit();
+        node.init();
+        assertThat(results).containsExactly(false);
 
-    results.clear();
-    node.destroy();
-    oss.setAppStateEntryValuesProto(
-        ImmutableMap.of(
-                keyFoo,
-                DynamicDataValue.newBuilder()
-                .setBoolVal(FixedBool.newBuilder().setValue(true))
-                .build()));
-    assertThat(results).isEmpty();
-  }
+        results.clear();
+        node.destroy();
+        oss.setAppStateEntryValuesProto(
+                ImmutableMap.of(
+                        keyFoo,
+                        DynamicDataValue.newBuilder()
+                                .setBoolVal(FixedBool.newBuilder().setValue(true))
+                                .build()));
+        assertThat(results).isEmpty();
+    }
 
-  @Test
-  public void logicalBoolOpTest() {
-    assertThat(evaluateLogicalOperation(LOGICAL_OP_TYPE_AND, true, true)).isTrue();
-    assertThat(evaluateLogicalOperation(LOGICAL_OP_TYPE_AND, true, false)).isFalse();
+    @Test
+    public void logicalBoolOpTest_unknownOp_throws() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> evaluateLogicalOperation(LOGICAL_OP_TYPE_UNDEFINED, true, true));
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> evaluateLogicalOperation(-1 /* UNRECOGNIZED */, true, true));
+    }
 
-    assertThat(evaluateLogicalOperation(LOGICAL_OP_TYPE_OR, true, false)).isTrue();
-    assertThat(evaluateLogicalOperation(LOGICAL_OP_TYPE_OR, false, false)).isFalse();
+    @Test
+    public void logicalBoolOpTest() {
+        assertThat(evaluateLogicalOperation(LOGICAL_OP_TYPE_AND, true, true)).isTrue();
+        assertThat(evaluateLogicalOperation(LOGICAL_OP_TYPE_AND, true, false)).isFalse();
 
-    assertThat(evaluateLogicalOperation(LOGICAL_OP_TYPE_EQUAL, true, true)).isTrue();
-    assertThat(evaluateLogicalOperation(LOGICAL_OP_TYPE_EQUAL, true, false)).isFalse();
+        assertThat(evaluateLogicalOperation(LOGICAL_OP_TYPE_OR, true, false)).isTrue();
+        assertThat(evaluateLogicalOperation(LOGICAL_OP_TYPE_OR, false, false)).isFalse();
 
-    assertThat(evaluateLogicalOperation(LOGICAL_OP_TYPE_NOT_EQUAL, true, false)).isTrue();
-    assertThat(evaluateLogicalOperation(LOGICAL_OP_TYPE_NOT_EQUAL, false, false)).isFalse();
-  }
+        assertThat(evaluateLogicalOperation(LOGICAL_OP_TYPE_EQUAL, true, true)).isTrue();
+        assertThat(evaluateLogicalOperation(LOGICAL_OP_TYPE_EQUAL, true, false)).isFalse();
 
-  private static boolean evaluateLogicalOperation(
-      DynamicProto.LogicalOpType logicalOpType, boolean lhs, boolean rhs) {
-    List<Boolean> results = new ArrayList<>();
+        assertThat(evaluateLogicalOperation(LOGICAL_OP_TYPE_NOT_EQUAL, true, false)).isTrue();
+        assertThat(evaluateLogicalOperation(LOGICAL_OP_TYPE_NOT_EQUAL, false, false)).isFalse();
+    }
 
-    LogicalBoolOp protoNode = LogicalBoolOp.newBuilder().setOperationType(logicalOpType).build();
-    BoolNodes.LogicalBoolOp node =
-        new BoolNodes.LogicalBoolOp(protoNode, new AddToListCallback<>(results));
+    @Test
+    public void int32CompareOp_unknownOp_throws() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> evaluateInt32ComparisonOperation(COMPARISON_OP_TYPE_UNDEFINED, 1, 1));
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> evaluateInt32ComparisonOperation(-1 /* UNRECOGNIZED */, 1, 1));
+    }
 
-    FixedBool lhsProtoNode = FixedBool.newBuilder().setValue(lhs).build();
-    FixedBoolNode lhsNode = new FixedBoolNode(lhsProtoNode, node.getLhsUpstreamCallback());
-    lhsNode.init();
+    @Test
+    public void int32CompareOpTest() {
+        assertThat(evaluateInt32ComparisonOperation(COMPARISON_OP_TYPE_EQUALS, 1, 1)).isTrue();
+        assertThat(evaluateInt32ComparisonOperation(COMPARISON_OP_TYPE_EQUALS, 1, 2)).isFalse();
 
-    FixedBool rhsProtoNode = FixedBool.newBuilder().setValue(rhs).build();
-    FixedBoolNode rhsNode = new FixedBoolNode(rhsProtoNode, node.getRhsUpstreamCallback());
-    rhsNode.init();
+        assertThat(evaluateInt32ComparisonOperation(COMPARISON_OP_TYPE_NOT_EQUALS, 1, 1)).isFalse();
+        assertThat(evaluateInt32ComparisonOperation(COMPARISON_OP_TYPE_NOT_EQUALS, 1, 2)).isTrue();
 
-    return results.get(0);
-  }
+        assertThat(evaluateInt32ComparisonOperation(COMPARISON_OP_TYPE_GREATER_THAN, 1, 2))
+                .isFalse();
+        assertThat(evaluateInt32ComparisonOperation(COMPARISON_OP_TYPE_GREATER_THAN, 1, 1))
+                .isFalse();
+        assertThat(evaluateInt32ComparisonOperation(COMPARISON_OP_TYPE_GREATER_THAN, 2, 1))
+                .isTrue();
+
+        assertThat(
+                        evaluateInt32ComparisonOperation(
+                                COMPARISON_OP_TYPE_GREATER_THAN_OR_EQUAL_TO, 1, 2))
+                .isFalse();
+        assertThat(
+                        evaluateInt32ComparisonOperation(
+                                COMPARISON_OP_TYPE_GREATER_THAN_OR_EQUAL_TO, 1, 1))
+                .isTrue();
+        assertThat(
+                        evaluateInt32ComparisonOperation(
+                                COMPARISON_OP_TYPE_GREATER_THAN_OR_EQUAL_TO, 2, 1))
+                .isTrue();
+
+        assertThat(evaluateInt32ComparisonOperation(COMPARISON_OP_TYPE_LESS_THAN, 2, 1)).isFalse();
+        assertThat(evaluateInt32ComparisonOperation(COMPARISON_OP_TYPE_LESS_THAN, 1, 1)).isFalse();
+        assertThat(evaluateInt32ComparisonOperation(COMPARISON_OP_TYPE_LESS_THAN, 1, 2)).isTrue();
+
+        assertThat(evaluateInt32ComparisonOperation(COMPARISON_OP_TYPE_LESS_THAN_OR_EQUAL_TO, 2, 1))
+                .isFalse();
+        assertThat(evaluateInt32ComparisonOperation(COMPARISON_OP_TYPE_LESS_THAN_OR_EQUAL_TO, 1, 1))
+                .isTrue();
+        assertThat(evaluateInt32ComparisonOperation(COMPARISON_OP_TYPE_LESS_THAN_OR_EQUAL_TO, 1, 2))
+                .isTrue();
+    }
+
+    @Test
+    public void floatCompareOp_unknownOp_throws() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> evaluateFloatComparisonOperation(COMPARISON_OP_TYPE_UNDEFINED, 1, 1));
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> evaluateFloatComparisonOperation(-1 /* UNRECOGNIZED */, 1, 1));
+    }
+
+    @Test
+    public void floatCompareOpTest() {
+        assertThat(evaluateFloatComparisonOperation(COMPARISON_OP_TYPE_EQUALS, 1, 1)).isTrue();
+        assertThat(evaluateFloatComparisonOperation(COMPARISON_OP_TYPE_EQUALS, 1, 2)).isFalse();
+
+        assertThat(evaluateFloatComparisonOperation(COMPARISON_OP_TYPE_NOT_EQUALS, 1, 1)).isFalse();
+        assertThat(evaluateFloatComparisonOperation(COMPARISON_OP_TYPE_NOT_EQUALS, 1, 2)).isTrue();
+
+        assertThat(evaluateFloatComparisonOperation(COMPARISON_OP_TYPE_GREATER_THAN, 1, 2))
+                .isFalse();
+        assertThat(evaluateFloatComparisonOperation(COMPARISON_OP_TYPE_GREATER_THAN, 1, 1))
+                .isFalse();
+        assertThat(evaluateFloatComparisonOperation(COMPARISON_OP_TYPE_GREATER_THAN, 2, 1))
+                .isTrue();
+
+        assertThat(
+                        evaluateFloatComparisonOperation(
+                                COMPARISON_OP_TYPE_GREATER_THAN_OR_EQUAL_TO, 1, 2))
+                .isFalse();
+        assertThat(
+                        evaluateFloatComparisonOperation(
+                                COMPARISON_OP_TYPE_GREATER_THAN_OR_EQUAL_TO, 1, 1))
+                .isTrue();
+        assertThat(
+                        evaluateFloatComparisonOperation(
+                                COMPARISON_OP_TYPE_GREATER_THAN_OR_EQUAL_TO, 2, 1))
+                .isTrue();
+
+        assertThat(evaluateFloatComparisonOperation(COMPARISON_OP_TYPE_LESS_THAN, 2, 1)).isFalse();
+        assertThat(evaluateFloatComparisonOperation(COMPARISON_OP_TYPE_LESS_THAN, 1, 1)).isFalse();
+        assertThat(evaluateFloatComparisonOperation(COMPARISON_OP_TYPE_LESS_THAN, 1, 2)).isTrue();
+
+        assertThat(evaluateFloatComparisonOperation(COMPARISON_OP_TYPE_LESS_THAN_OR_EQUAL_TO, 2, 1))
+                .isFalse();
+        assertThat(evaluateFloatComparisonOperation(COMPARISON_OP_TYPE_LESS_THAN_OR_EQUAL_TO, 1, 1))
+                .isTrue();
+        assertThat(evaluateFloatComparisonOperation(COMPARISON_OP_TYPE_LESS_THAN_OR_EQUAL_TO, 1, 2))
+                .isTrue();
+    }
+
+    private static boolean evaluateLogicalOperation(
+            DynamicProto.LogicalOpType logicalOpType, boolean lhs, boolean rhs) {
+        return evaluateLogicalOperation(logicalOpType.getNumber(), lhs, rhs);
+    }
+
+    private static boolean evaluateLogicalOperation(int logicalOpType, boolean lhs, boolean rhs) {
+        List<Boolean> results = new ArrayList<>();
+
+        LogicalBoolOp protoNode =
+                LogicalBoolOp.newBuilder().setOperationTypeValue(logicalOpType).build();
+        BoolNodes.LogicalBoolOp node =
+                new BoolNodes.LogicalBoolOp(protoNode, new AddToListCallback<>(results));
+
+        FixedBool lhsProtoNode = FixedBool.newBuilder().setValue(lhs).build();
+        FixedBoolNode lhsNode = new FixedBoolNode(lhsProtoNode, node.getLhsUpstreamCallback());
+
+        FixedBool rhsProtoNode = FixedBool.newBuilder().setValue(rhs).build();
+        FixedBoolNode rhsNode = new FixedBoolNode(rhsProtoNode, node.getRhsUpstreamCallback());
+
+        lhsNode.preInit();
+        rhsNode.preInit();
+
+        lhsNode.init();
+        rhsNode.init();
+
+        return results.get(0);
+    }
+
+    private static boolean evaluateInt32ComparisonOperation(
+            DynamicProto.ComparisonOpType opType, int lhs, int rhs) {
+        return evaluateInt32ComparisonOperation(opType.getNumber(), lhs, rhs);
+    }
+
+    private static boolean evaluateInt32ComparisonOperation(int opType, int lhs, int rhs) {
+        List<Boolean> results = new ArrayList<>();
+
+        DynamicProto.ComparisonInt32Op protoNode =
+                DynamicProto.ComparisonInt32Op.newBuilder().setOperationTypeValue(opType).build();
+        BoolNodes.ComparisonInt32Node node =
+                new BoolNodes.ComparisonInt32Node(protoNode, new AddToListCallback<>(results));
+
+        FixedInt32 lhsProtoNode = FixedInt32.newBuilder().setValue(lhs).build();
+        FixedInt32Node lhsNode = new FixedInt32Node(lhsProtoNode, node.getLhsUpstreamCallback());
+        lhsNode.preInit();
+
+        FixedInt32 rhsProtoNode = FixedInt32.newBuilder().setValue(rhs).build();
+        FixedInt32Node rhsNode = new FixedInt32Node(rhsProtoNode, node.getRhsUpstreamCallback());
+        rhsNode.preInit();
+
+        lhsNode.init();
+        rhsNode.init();
+
+        return results.get(0);
+    }
+
+    private static boolean evaluateFloatComparisonOperation(
+            DynamicProto.ComparisonOpType opType, float lhs, float rhs) {
+        return evaluateFloatComparisonOperation(opType.getNumber(), lhs, rhs);
+    }
+
+    private static boolean evaluateFloatComparisonOperation(int opType, float lhs, float rhs) {
+        List<Boolean> results = new ArrayList<>();
+
+        DynamicProto.ComparisonFloatOp protoNode =
+                DynamicProto.ComparisonFloatOp.newBuilder().setOperationTypeValue(opType).build();
+        BoolNodes.ComparisonFloatNode node =
+                new BoolNodes.ComparisonFloatNode(protoNode, new AddToListCallback<>(results));
+
+        FixedFloat lhsProtoNode = FixedFloat.newBuilder().setValue(lhs).build();
+        FixedFloatNode lhsNode = new FixedFloatNode(lhsProtoNode, node.getLhsUpstreamCallback());
+        lhsNode.preInit();
+
+        FixedFloat rhsProtoNode = FixedFloat.newBuilder().setValue(rhs).build();
+        FixedFloatNode rhsNode = new FixedFloatNode(rhsProtoNode, node.getRhsUpstreamCallback());
+        rhsNode.preInit();
+
+        lhsNode.init();
+        rhsNode.init();
+
+        return results.get(0);
+    }
 }
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluatorTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluatorTest.java
index 55d6ee1..dca1d9e 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluatorTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/DynamicTypeEvaluatorTest.java
@@ -33,6 +33,8 @@
 import androidx.wear.protolayout.expression.PlatformDataKey;
 import androidx.wear.protolayout.expression.PlatformHealthSources;
 import androidx.wear.protolayout.expression.pipeline.DynamicTypeEvaluator.EvaluationException;
+import androidx.wear.protolayout.expression.proto.DynamicProto;
+import androidx.wear.protolayout.expression.proto.FixedProto;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -56,6 +58,16 @@
     }
 
     @Test
+    public void evaluateBindingRequest_nodeThrows_propagateTheException() {
+        DynamicTypeEvaluator evaluator = createEvaluator();
+        ArrayList<Integer> results = new ArrayList<>();
+        DynamicTypeBindingRequest request = createExpressionWithUnrecognizedEnum(results);
+
+        assertThrows(
+                IllegalArgumentException.class, () -> evaluator.bind(request).startEvaluation());
+    }
+
+    @Test
     public void evaluateBindingRequest_insufficientDynamicNodeQuota_throws() {
         DynamicTypeEvaluator evaluator =
                 createEvaluatorWithQuota(
@@ -110,11 +122,10 @@
         AddToListCallback<Integer> results = new AddToListCallback<>(new ArrayList<>());
         DynamicTypeBindingRequest request =
                 DynamicTypeBindingRequest.forDynamicInt32(
-                        PlatformHealthSources.dailySteps(),
-                        new MainThreadExecutor(), results);
+                        PlatformHealthSources.dailySteps(), new MainThreadExecutor(), results);
         PlatformDataProvider provider = mock(PlatformDataProvider.class);
-        DynamicTypeEvaluator evaluator = createEvaluatorWithProvider(provider,
-                PlatformHealthSources.Keys.DAILY_STEPS);
+        DynamicTypeEvaluator evaluator =
+                createEvaluatorWithProvider(provider, PlatformHealthSources.Keys.DAILY_STEPS);
 
         BoundDynamicType boundDynamicType = evaluator.bind(request);
         boundDynamicType.startEvaluation();
@@ -133,8 +144,8 @@
                         /* animationQuota= */ unlimitedQuota(),
                         /* dynamicTypesQuota= */ new FixedQuotaManagerImpl(1));
         ArrayList<Boolean> results = new ArrayList<>();
-        BoundDynamicType boundDynamicType = evaluator.bind(
-                createSingleNodeDynamicBoolRequest(results));
+        BoundDynamicType boundDynamicType =
+                evaluator.bind(createSingleNodeDynamicBoolRequest(results));
 
         for (int i = 0; i < 10; i++) {
             boundDynamicType.close();
@@ -151,12 +162,33 @@
     }
 
     @NonNull
+    private static DynamicTypeBindingRequest createExpressionWithUnrecognizedEnum(
+            ArrayList<Integer> results) {
+        return DynamicTypeBindingRequest.forDynamicInt32Internal(
+                DynamicProto.DynamicInt32.newBuilder()
+                        .setFloatToInt(
+                                DynamicProto.FloatToInt32Op.newBuilder()
+                                        .setInput(
+                                                DynamicProto.DynamicFloat.newBuilder()
+                                                        .setFixed(
+                                                                FixedProto.FixedFloat
+                                                                        .getDefaultInstance())
+                                                        .build())
+                                        .setRoundModeValue(-1)
+                                        .build())
+                        .build(),
+                new AddToListCallback<Integer>(results));
+    }
+
+    @NonNull
     private static DynamicTypeBindingRequest createSingleNodeDynamicStringFromTimePlatformRequest(
             ArrayList<String> results) {
         return DynamicTypeBindingRequest.forDynamicString(
-                DynamicBuilders.DynamicInstant.platformTimeWithSecondsPrecision().durationUntil(
-                        DynamicBuilders.DynamicInstant
-                                .withSecondsPrecision(Instant.now())).getSecondsPart().format(),
+                DynamicBuilders.DynamicInstant.platformTimeWithSecondsPrecision()
+                        .durationUntil(
+                                DynamicBuilders.DynamicInstant.withSecondsPrecision(Instant.now()))
+                        .getSecondsPart()
+                        .format(),
                 ULocale.ENGLISH,
                 new MainThreadExecutor(),
                 new AddToListCallback<>(results));
@@ -166,8 +198,8 @@
         return createEvaluatorWithQuota(unlimitedQuota(), unlimitedQuota());
     }
 
-    private static DynamicTypeEvaluator createEvaluatorWithProvider(PlatformDataProvider provider
-            , PlatformDataKey<?> key) {
+    private static DynamicTypeEvaluator createEvaluatorWithProvider(
+            PlatformDataProvider provider, PlatformDataKey<?> key) {
         return new DynamicTypeEvaluator(
                 new DynamicTypeEvaluator.Config.Builder()
                         .setAnimationQuotaManager(unlimitedQuota())
@@ -216,8 +248,7 @@
         }
 
         @Override
-        public void setReceiver(
-                @NonNull Executor executor, @NonNull Runnable tick) {
+        public void setReceiver(@NonNull Executor executor, @NonNull Runnable tick) {
             super.setReceiver(executor, tick);
 
             mRegisteredReceiver = tick;
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/FloatNodeTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/FloatNodeTest.java
index c8aea06..1ca5905 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/FloatNodeTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/FloatNodeTest.java
@@ -20,6 +20,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.verify;
 import static org.robolectric.Shadows.shadowOf;
@@ -345,30 +346,35 @@
     }
 
     @Test
+    public void arithmeticFloat_unknownOperation_throws() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->
+                        evaluateArithmeticExpression(
+                                /* lhs= */ 1,
+                                /* rhs= */ 1,
+                                ArithmeticOpType.ARITHMETIC_OP_TYPE_UNDEFINED,
+                                new AddToListCallback<>(new ArrayList<>())));
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->
+                        evaluateArithmeticExpression(
+                                /* lhs= */ 1,
+                                /* rhs= */ 1,
+                                -1 /* UNRECOGNIZED */,
+                                new AddToListCallback<>(new ArrayList<>())));
+    }
+
+    @Test
     public void arithmeticFloat_resultIsNaN_invalidate() {
         List<Float> results = new ArrayList<>();
         List<Boolean> invalidList = new ArrayList<>();
 
-        ArithmeticFloatOp protoNode =
-                ArithmeticFloatOp.newBuilder()
-                        .setOperationType(ArithmeticOpType.ARITHMETIC_OP_TYPE_DIVIDE)
-                        .build();
-
-        ArithmeticFloatNode node =
-                new ArithmeticFloatNode(protoNode, new AddToListCallback<>(results, invalidList));
-
-        float numerator = 0f;
-        FixedFloat lhsProtoNode = FixedFloat.newBuilder().setValue(numerator).build();
-        FixedFloatNode lhsNode = new FixedFloatNode(lhsProtoNode, node.getLhsUpstreamCallback());
-
-        float denominator = 0f;
-        FixedFloat rhsProtoNode = FixedFloat.newBuilder().setValue(denominator).build();
-        FixedFloatNode rhsNode = new FixedFloatNode(rhsProtoNode, node.getRhsUpstreamCallback());
-        lhsNode.preInit();
-        rhsNode.preInit();
-
-        lhsNode.init();
-        rhsNode.init();
+        evaluateArithmeticExpression(
+                /* lhs= */ 0,
+                /* rhs= */ 0,
+                ArithmeticOpType.ARITHMETIC_OP_TYPE_DIVIDE,
+                new AddToListCallback<>(results, invalidList));
 
         assertThat(results).isEmpty();
         assertThat(invalidList).containsExactly(true);
@@ -379,26 +385,11 @@
         List<Float> results = new ArrayList<>();
         List<Boolean> invalidList = new ArrayList<>();
 
-        ArithmeticFloatOp protoNode =
-                ArithmeticFloatOp.newBuilder()
-                        .setOperationType(ArithmeticOpType.ARITHMETIC_OP_TYPE_DIVIDE)
-                        .build();
-
-        ArithmeticFloatNode node =
-                new ArithmeticFloatNode(protoNode, new AddToListCallback<>(results, invalidList));
-
-        float numerator = 1f;
-        FixedFloat lhsProtoNode = FixedFloat.newBuilder().setValue(numerator).build();
-        FixedFloatNode lhsNode = new FixedFloatNode(lhsProtoNode, node.getLhsUpstreamCallback());
-
-        float denominator = 0;
-        FixedFloat rhsProtoNode = FixedFloat.newBuilder().setValue(denominator).build();
-        FixedFloatNode rhsNode = new FixedFloatNode(rhsProtoNode, node.getRhsUpstreamCallback());
-        lhsNode.preInit();
-        rhsNode.preInit();
-
-        lhsNode.init();
-        rhsNode.init();
+        evaluateArithmeticExpression(
+                /* lhs= */ 1,
+                /* rhs= */ 0,
+                ArithmeticOpType.ARITHMETIC_OP_TYPE_DIVIDE,
+                new AddToListCallback<>(results, invalidList));
 
         assertThat(results).isEmpty();
         assertThat(invalidList).containsExactly(true);
@@ -570,4 +561,31 @@
         assertThat(Iterables.getLast(results)).isEqualTo(value3);
         assertThat(results).isInOrder();
     }
+
+    private static void evaluateArithmeticExpression(
+            float lhs,
+            float rhs,
+            ArithmeticOpType op,
+            DynamicTypeValueReceiverWithPreUpdate<Float> receiver) {
+        evaluateArithmeticExpression(lhs, rhs, op.getNumber(), receiver);
+    }
+
+    private static void evaluateArithmeticExpression(
+            float lhs, float rhs, int op, DynamicTypeValueReceiverWithPreUpdate<Float> receiver) {
+        ArithmeticFloatOp protoNode =
+                ArithmeticFloatOp.newBuilder().setOperationTypeValue(op).build();
+
+        ArithmeticFloatNode node = new ArithmeticFloatNode(protoNode, receiver);
+
+        FixedFloat lhsProtoNode = FixedFloat.newBuilder().setValue(lhs).build();
+        FixedFloatNode lhsNode = new FixedFloatNode(lhsProtoNode, node.getLhsUpstreamCallback());
+
+        FixedFloat rhsProtoNode = FixedFloat.newBuilder().setValue(rhs).build();
+        FixedFloatNode rhsNode = new FixedFloatNode(rhsProtoNode, node.getRhsUpstreamCallback());
+        lhsNode.preInit();
+        rhsNode.preInit();
+
+        lhsNode.init();
+        rhsNode.init();
+    }
 }
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/Int32NodesTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/Int32NodesTest.java
index 1771223..8b7fce5 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/Int32NodesTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/Int32NodesTest.java
@@ -18,9 +18,16 @@
 
 import static androidx.wear.protolayout.expression.PlatformHealthSources.Keys.DAILY_STEPS;
 import static androidx.wear.protolayout.expression.PlatformHealthSources.Keys.HEART_RATE_BPM;
+import static androidx.wear.protolayout.expression.proto.DynamicProto.ArithmeticOpType.ARITHMETIC_OP_TYPE_DIVIDE;
+import static androidx.wear.protolayout.expression.proto.DynamicProto.ArithmeticOpType.ARITHMETIC_OP_TYPE_UNDEFINED;
+import static androidx.wear.protolayout.expression.proto.DynamicProto.FloatToInt32RoundMode.ROUND_MODE_CEILING;
+import static androidx.wear.protolayout.expression.proto.DynamicProto.FloatToInt32RoundMode.ROUND_MODE_FLOOR;
+import static androidx.wear.protolayout.expression.proto.DynamicProto.FloatToInt32RoundMode.ROUND_MODE_ROUND;
+import static androidx.wear.protolayout.expression.proto.DynamicProto.FloatToInt32RoundMode.ROUND_MODE_UNDEFINED;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.verify;
 import static org.robolectric.Shadows.shadowOf;
@@ -46,7 +53,6 @@
 import androidx.wear.protolayout.expression.proto.DynamicDataProto.DynamicDataValue;
 import androidx.wear.protolayout.expression.proto.DynamicProto;
 import androidx.wear.protolayout.expression.proto.DynamicProto.AnimatableFixedInt32;
-import androidx.wear.protolayout.expression.proto.DynamicProto.ArithmeticOpType;
 import androidx.wear.protolayout.expression.proto.DynamicProto.DurationPartType;
 import androidx.wear.protolayout.expression.proto.DynamicProto.GetDurationPartOp;
 import androidx.wear.protolayout.expression.proto.DynamicProto.GetZonedDateTimePartOp;
@@ -99,32 +105,37 @@
     }
 
     @Test
+    public void testArithmeticOperation_unknownOp_throws() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->
+                        evaluateArithmeticExpression(
+                                1,
+                                1,
+                                ARITHMETIC_OP_TYPE_UNDEFINED.getNumber(),
+                                new AddToListCallback<>(new ArrayList<>())));
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->
+                        evaluateArithmeticExpression(
+                                /* lhs= */ 1,
+                                /* rhs= */ 1,
+                                -1 /* UNRECOGNIZED */,
+                                new AddToListCallback<>(new ArrayList<>())));
+    }
+
+    @Test
     public void testArithmeticOperation_validResult_invalidateNotCalled() {
         List<Integer> results = new ArrayList<>();
         List<Boolean> invalidList = new ArrayList<>();
 
-        DynamicProto.ArithmeticInt32Op protoNode =
-                DynamicProto.ArithmeticInt32Op.newBuilder()
-                        .setOperationType(ArithmeticOpType.ARITHMETIC_OP_TYPE_DIVIDE)
-                        .build();
+        evaluateArithmeticExpression(
+                /* lhs= */ 4,
+                /* rhs= */ 3,
+                ARITHMETIC_OP_TYPE_DIVIDE.getNumber(),
+                new AddToListCallback<>(results, invalidList));
 
-        ArithmeticInt32Node node =
-                new ArithmeticInt32Node(protoNode, new AddToListCallback<>(results, invalidList));
-
-        int numerator = 4;
-        FixedInt32 lhsProtoNode = FixedInt32.newBuilder().setValue(numerator).build();
-        FixedInt32Node lhsNode = new FixedInt32Node(lhsProtoNode, node.getLhsUpstreamCallback());
-
-        int denominator = 2;
-        FixedInt32 rhsProtoNode = FixedInt32.newBuilder().setValue(denominator).build();
-        FixedInt32Node rhsNode = new FixedInt32Node(rhsProtoNode, node.getRhsUpstreamCallback());
-        lhsNode.preInit();
-        rhsNode.preInit();
-
-        lhsNode.init();
-        rhsNode.init();
-
-        assertThat(results).containsExactly(2);
+        assertThat(results).containsExactly(1);
         assertThat(invalidList).isEmpty();
     }
 
@@ -133,32 +144,32 @@
         List<Integer> results = new ArrayList<>();
         List<Boolean> invalidList = new ArrayList<>();
 
-        DynamicProto.ArithmeticInt32Op protoNode =
-                DynamicProto.ArithmeticInt32Op.newBuilder()
-                        .setOperationType(ArithmeticOpType.ARITHMETIC_OP_TYPE_DIVIDE)
-                        .build();
-
-        ArithmeticInt32Node node =
-                new ArithmeticInt32Node(protoNode, new AddToListCallback<>(results, invalidList));
-
-        int numerator = 0;
-        FixedInt32 lhsProtoNode = FixedInt32.newBuilder().setValue(numerator).build();
-        FixedInt32Node lhsNode = new FixedInt32Node(lhsProtoNode, node.getLhsUpstreamCallback());
-
-        int denominator = 0;
-        FixedInt32 rhsProtoNode = FixedInt32.newBuilder().setValue(denominator).build();
-        FixedInt32Node rhsNode = new FixedInt32Node(rhsProtoNode, node.getRhsUpstreamCallback());
-        lhsNode.preInit();
-        rhsNode.preInit();
-
-        lhsNode.init();
-        rhsNode.init();
+        evaluateArithmeticExpression(
+                /* lhs= */ 0,
+                /* rhs= */ 0,
+                ARITHMETIC_OP_TYPE_DIVIDE.getNumber(),
+                new AddToListCallback<>(results, invalidList));
 
         assertThat(results).isEmpty();
         assertThat(invalidList).containsExactly(true);
     }
 
     @Test
+    public void testGetDurationPartOpNode_unknownPart_throws() {
+        Duration duration = Duration.ofSeconds(123456);
+
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->
+                        createGetDurationPartOpNodeAndGetPart(
+                                duration, DurationPartType.DURATION_PART_TYPE_UNDEFINED));
+
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> createGetDurationPartOpNodeAndGetPart(duration, -1 /* UNRECOGNIZED */));
+    }
+
+    @Test
     public void testGetDurationPartOpNode_positiveDuration() {
 
         // Equivalent to 1day and 10h:17m:36s
@@ -239,10 +250,14 @@
     }
 
     private int createGetDurationPartOpNodeAndGetPart(Duration duration, DurationPartType part) {
+        return createGetDurationPartOpNodeAndGetPart(duration, part.getNumber());
+    }
+
+    private int createGetDurationPartOpNodeAndGetPart(Duration duration, int part) {
         List<Integer> results = new ArrayList<>();
         GetDurationPartOpNode node =
                 new GetDurationPartOpNode(
-                        GetDurationPartOp.newBuilder().setDurationPart(part).build(),
+                        GetDurationPartOp.newBuilder().setDurationPartValue(part).build(),
                         new AddToListCallback<>(results));
         node.getIncomingCallback().onData(duration);
         return results.get(0);
@@ -632,4 +647,60 @@
                                 zonedDateTime, ZonedDateTimePartType.ZONED_DATE_TIME_PART_SECOND))
                 .isEqualTo(0);
     }
+
+    @Test
+    public void testFloatToInt32Node() {
+        assertThat(evaluateFloatToInt32Expression(12.49f, ROUND_MODE_CEILING)).isEqualTo(13);
+        assertThat(evaluateFloatToInt32Expression(12.99f, ROUND_MODE_FLOOR)).isEqualTo(12);
+        assertThat(evaluateFloatToInt32Expression(12.49f, ROUND_MODE_ROUND)).isEqualTo(12);
+        assertThat(evaluateFloatToInt32Expression(12.50f, ROUND_MODE_ROUND)).isEqualTo(13);
+    }
+
+    @Test
+    public void testFloatToInt32Node__unknownRoundType_throws() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> evaluateFloatToInt32Expression(12.34f, ROUND_MODE_UNDEFINED));
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> evaluateFloatToInt32Expression(12.34f, -1 /* UNRECOGNIZED */));
+    }
+
+    private static void evaluateArithmeticExpression(
+            int lhs, int rhs, int opType, DynamicTypeValueReceiverWithPreUpdate<Integer> receiver) {
+        DynamicProto.ArithmeticInt32Op protoNode =
+                DynamicProto.ArithmeticInt32Op.newBuilder().setOperationTypeValue(opType).build();
+
+        ArithmeticInt32Node node = new ArithmeticInt32Node(protoNode, receiver);
+
+        FixedInt32 lhsProtoNode = FixedInt32.newBuilder().setValue(lhs).build();
+        FixedInt32Node lhsNode = new FixedInt32Node(lhsProtoNode, node.getLhsUpstreamCallback());
+
+        FixedInt32 rhsProtoNode = FixedInt32.newBuilder().setValue(rhs).build();
+        FixedInt32Node rhsNode = new FixedInt32Node(rhsProtoNode, node.getRhsUpstreamCallback());
+        lhsNode.preInit();
+        rhsNode.preInit();
+
+        lhsNode.init();
+        rhsNode.init();
+    }
+
+    private static int evaluateFloatToInt32Expression(
+            float value, DynamicProto.FloatToInt32RoundMode roundMode) {
+        return evaluateFloatToInt32Expression(value, roundMode.getNumber());
+    }
+
+    private static int evaluateFloatToInt32Expression(float value, int roundMode) {
+        List<Integer> results = new ArrayList<>();
+        Int32Nodes.FloatToInt32Node node =
+                new Int32Nodes.FloatToInt32Node(
+                        DynamicProto.FloatToInt32Op.newBuilder()
+                                .setRoundModeValue(roundMode)
+                                .build(),
+                        new AddToListCallback<>(results));
+        node.getIncomingCallback().onPreUpdate();
+        node.getIncomingCallback().onData(value);
+
+        return results.get(0);
+    }
 }
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/ParametrizedDynamicTypeEvaluatorTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/ParametrizedDynamicTypeEvaluatorTest.java
index f68ef36..b839904 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/ParametrizedDynamicTypeEvaluatorTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/ParametrizedDynamicTypeEvaluatorTest.java
@@ -40,6 +40,7 @@
 import androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32;
 import androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32.IntFormatter;
 import androidx.wear.protolayout.expression.DynamicBuilders.DynamicString;
+import androidx.wear.protolayout.expression.pipeline.DynamicTypeEvaluator.EvaluationException;
 import androidx.wear.protolayout.expression.proto.DynamicDataProto.DynamicDataValue;
 import androidx.wear.protolayout.expression.proto.FixedProto.FixedBool;
 import androidx.wear.protolayout.expression.proto.FixedProto.FixedFloat;
@@ -380,14 +381,20 @@
             DynamicString bindUnderTest, String expectedValue) {
         return new ParametrizedDynamicTypeEvaluatorTest.TestCase<>(
                 bindUnderTest.toDynamicStringProto().toString(),
-                (evaluator, cb) ->
+                (evaluator, cb) -> {
+                    try {
                         evaluator
-                                .bindInternal(
-                                        bindUnderTest,
-                                        ULocale.getDefault(),
-                                        new MainThreadExecutor(),
-                                        cb)
-                                .startEvaluation(),
+                                .bind(
+                                        DynamicTypeBindingRequest.forDynamicString(
+                                                bindUnderTest,
+                                                ULocale.getDefault(),
+                                                new MainThreadExecutor(),
+                                                cb))
+                                .startEvaluation();
+                    } catch (EvaluationException e) {
+                        throw new RuntimeException(e);
+                    }
+                },
                 expectedValue);
     }
 
@@ -395,10 +402,17 @@
             DynamicInt32 bindUnderTest, Integer expectedValue) {
         return new ParametrizedDynamicTypeEvaluatorTest.TestCase<>(
                 bindUnderTest.toDynamicInt32Proto().toString(),
-                (evaluator, cb) ->
+                (evaluator, cb) -> {
+                    try {
                         evaluator
-                                .bindInternal(bindUnderTest, new MainThreadExecutor(), cb)
-                                .startEvaluation(),
+                                .bind(
+                                        DynamicTypeBindingRequest.forDynamicInt32(
+                                                bindUnderTest, new MainThreadExecutor(), cb))
+                                .startEvaluation();
+                    } catch (EvaluationException e) {
+                        throw new RuntimeException(e);
+                    }
+                },
                 expectedValue);
     }
 
@@ -406,10 +420,17 @@
             DynamicColor bindUnderTest, Integer expectedValue) {
         return new ParametrizedDynamicTypeEvaluatorTest.TestCase<>(
                 bindUnderTest.toDynamicColorProto().toString(),
-                (evaluator, cb) ->
+                (evaluator, cb) -> {
+                    try {
                         evaluator
-                                .bindInternal(bindUnderTest, new MainThreadExecutor(), cb)
-                                .startEvaluation(),
+                                .bind(
+                                        DynamicTypeBindingRequest.forDynamicColor(
+                                                bindUnderTest, new MainThreadExecutor(), cb))
+                                .startEvaluation();
+                    } catch (EvaluationException e) {
+                        throw new RuntimeException(e);
+                    }
+                },
                 expectedValue);
     }
 
@@ -417,10 +438,17 @@
             DynamicInstant bindUnderTest, Instant instant) {
         return new ParametrizedDynamicTypeEvaluatorTest.TestCase<>(
                 bindUnderTest.toDynamicInstantProto().toString(),
-                (evaluator, cb) ->
+                (evaluator, cb) -> {
+                    try {
                         evaluator
-                                .bindInternal(bindUnderTest, new MainThreadExecutor(), cb)
-                                .startEvaluation(),
+                                .bind(
+                                        DynamicTypeBindingRequest.forDynamicInstant(
+                                                bindUnderTest, new MainThreadExecutor(), cb))
+                                .startEvaluation();
+                    } catch (EvaluationException e) {
+                        throw new RuntimeException(e);
+                    }
+                },
                 instant);
     }
 
@@ -428,10 +456,17 @@
             DynamicDuration bindUnderTest, Duration duration) {
         return new ParametrizedDynamicTypeEvaluatorTest.TestCase<>(
                 bindUnderTest.toDynamicDurationProto().toString(),
-                (evaluator, cb) ->
+                (evaluator, cb) -> {
+                    try {
                         evaluator
-                                .bindInternal(bindUnderTest, new MainThreadExecutor(), cb)
-                                .startEvaluation(),
+                                .bind(
+                                        DynamicTypeBindingRequest.forDynamicDuration(
+                                                bindUnderTest, new MainThreadExecutor(), cb))
+                                .startEvaluation();
+                    } catch (EvaluationException e) {
+                        throw new RuntimeException(e);
+                    }
+                },
                 duration);
     }
 
@@ -439,10 +474,17 @@
             DynamicFloat bindUnderTest, Float expectedValue) {
         return new ParametrizedDynamicTypeEvaluatorTest.TestCase<>(
                 bindUnderTest.toDynamicFloatProto().toString(),
-                (evaluator, cb) ->
+                (evaluator, cb) -> {
+                    try {
                         evaluator
-                                .bindInternal(bindUnderTest, new MainThreadExecutor(), cb)
-                                .startEvaluation(),
+                                .bind(
+                                        DynamicTypeBindingRequest.forDynamicFloat(
+                                                bindUnderTest, new MainThreadExecutor(), cb))
+                                .startEvaluation();
+                    } catch (EvaluationException e) {
+                        throw new RuntimeException(e);
+                    }
+                },
                 expectedValue);
     }
 
@@ -450,10 +492,17 @@
             DynamicBool bindUnderTest, Boolean expectedValue) {
         return new ParametrizedDynamicTypeEvaluatorTest.TestCase<>(
                 bindUnderTest.toDynamicBoolProto().toString(),
-                (evaluator, cb) ->
+                (evaluator, cb) -> {
+                    try {
                         evaluator
-                                .bindInternal(bindUnderTest, new MainThreadExecutor(), cb)
-                                .startEvaluation(),
+                                .bind(
+                                        DynamicTypeBindingRequest.forDynamicBool(
+                                                bindUnderTest, new MainThreadExecutor(), cb))
+                                .startEvaluation();
+                    } catch (EvaluationException e) {
+                        throw new RuntimeException(e);
+                    }
+                },
                 expectedValue);
     }
 
@@ -461,20 +510,34 @@
             DynamicInt32 bindUnderTest) {
         return new ParametrizedDynamicTypeEvaluatorTest.TestCase<>(
                 bindUnderTest.toDynamicInt32Proto().toString(),
-                (evaluator, cb) ->
+                (evaluator, cb) -> {
+                    try {
                         evaluator
-                                .bindInternal(bindUnderTest, new MainThreadExecutor(), cb)
-                                .startEvaluation());
+                                .bind(
+                                        DynamicTypeBindingRequest.forDynamicInt32(
+                                                bindUnderTest, new MainThreadExecutor(), cb))
+                                .startEvaluation();
+                    } catch (EvaluationException e) {
+                        throw new RuntimeException(e);
+                    }
+                });
     }
 
     private static ParametrizedDynamicTypeEvaluatorTest.TestCase<Float> testForInvalidValue(
             DynamicFloat bindUnderTest) {
         return new ParametrizedDynamicTypeEvaluatorTest.TestCase<>(
                 bindUnderTest.toDynamicFloatProto().toString(),
-                (evaluator, cb) ->
+                (evaluator, cb) -> {
+                    try {
                         evaluator
-                                .bindInternal(bindUnderTest, new MainThreadExecutor(), cb)
-                                .startEvaluation());
+                                .bind(
+                                        DynamicTypeBindingRequest.forDynamicFloat(
+                                                bindUnderTest, new MainThreadExecutor(), cb))
+                                .startEvaluation();
+                    } catch (EvaluationException e) {
+                        throw new RuntimeException(e);
+                    }
+                });
     }
 
     private static class TestCase<T> {
diff --git a/wear/protolayout/protolayout-material/api/current.txt b/wear/protolayout/protolayout-material/api/current.txt
index d73127d..01b19f2 100644
--- a/wear/protolayout/protolayout-material/api/current.txt
+++ b/wear/protolayout/protolayout-material/api/current.txt
@@ -138,14 +138,12 @@
     method public androidx.wear.protolayout.ModifiersBuilders.Clickable getClickable();
     method public String? getIconContent();
     method public String getText();
-    method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public boolean hasExcludeFontPadding();
   }
 
   public static final class CompactChip.Builder {
     ctor public CompactChip.Builder(android.content.Context, String, androidx.wear.protolayout.ModifiersBuilders.Clickable, androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
     method public androidx.wear.protolayout.material.CompactChip build();
     method public androidx.wear.protolayout.material.CompactChip.Builder setChipColors(androidx.wear.protolayout.material.ChipColors);
-    method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public androidx.wear.protolayout.material.CompactChip.Builder setExcludeFontPadding(boolean);
     method public androidx.wear.protolayout.material.CompactChip.Builder setIconContent(String);
   }
 
@@ -175,7 +173,6 @@
     method public int getOverflow();
     method public androidx.wear.protolayout.TypeBuilders.StringProp getText();
     method public int getWeight();
-    method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public boolean hasExcludeFontPadding();
     method public boolean isItalic();
     method public boolean isUnderline();
   }
@@ -185,7 +182,6 @@
     ctor public Text.Builder(android.content.Context, String);
     method public androidx.wear.protolayout.material.Text build();
     method public androidx.wear.protolayout.material.Text.Builder setColor(androidx.wear.protolayout.ColorBuilders.ColorProp);
-    method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public androidx.wear.protolayout.material.Text.Builder setExcludeFontPadding(boolean);
     method public androidx.wear.protolayout.material.Text.Builder setItalic(boolean);
     method public androidx.wear.protolayout.material.Text.Builder setMaxLines(@IntRange(from=1) int);
     method public androidx.wear.protolayout.material.Text.Builder setModifiers(androidx.wear.protolayout.ModifiersBuilders.Modifiers);
@@ -204,14 +200,12 @@
     method public String? getIconContent();
     method public String getText();
     method public androidx.wear.protolayout.DimensionBuilders.ContainerDimension getWidth();
-    method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public boolean hasExcludeFontPadding();
   }
 
   public static final class TitleChip.Builder {
     ctor public TitleChip.Builder(android.content.Context, String, androidx.wear.protolayout.ModifiersBuilders.Clickable, androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
     method public androidx.wear.protolayout.material.TitleChip build();
     method public androidx.wear.protolayout.material.TitleChip.Builder setChipColors(androidx.wear.protolayout.material.ChipColors);
-    method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public androidx.wear.protolayout.material.TitleChip.Builder setExcludeFontPadding(boolean);
     method public androidx.wear.protolayout.material.TitleChip.Builder setHorizontalAlignment(int);
     method public androidx.wear.protolayout.material.TitleChip.Builder setIconContent(String);
     method public androidx.wear.protolayout.material.TitleChip.Builder setWidth(androidx.wear.protolayout.DimensionBuilders.ContainerDimension);
diff --git a/wear/protolayout/protolayout-material/api/restricted_current.txt b/wear/protolayout/protolayout-material/api/restricted_current.txt
index d73127d..01b19f2 100644
--- a/wear/protolayout/protolayout-material/api/restricted_current.txt
+++ b/wear/protolayout/protolayout-material/api/restricted_current.txt
@@ -138,14 +138,12 @@
     method public androidx.wear.protolayout.ModifiersBuilders.Clickable getClickable();
     method public String? getIconContent();
     method public String getText();
-    method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public boolean hasExcludeFontPadding();
   }
 
   public static final class CompactChip.Builder {
     ctor public CompactChip.Builder(android.content.Context, String, androidx.wear.protolayout.ModifiersBuilders.Clickable, androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
     method public androidx.wear.protolayout.material.CompactChip build();
     method public androidx.wear.protolayout.material.CompactChip.Builder setChipColors(androidx.wear.protolayout.material.ChipColors);
-    method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public androidx.wear.protolayout.material.CompactChip.Builder setExcludeFontPadding(boolean);
     method public androidx.wear.protolayout.material.CompactChip.Builder setIconContent(String);
   }
 
@@ -175,7 +173,6 @@
     method public int getOverflow();
     method public androidx.wear.protolayout.TypeBuilders.StringProp getText();
     method public int getWeight();
-    method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public boolean hasExcludeFontPadding();
     method public boolean isItalic();
     method public boolean isUnderline();
   }
@@ -185,7 +182,6 @@
     ctor public Text.Builder(android.content.Context, String);
     method public androidx.wear.protolayout.material.Text build();
     method public androidx.wear.protolayout.material.Text.Builder setColor(androidx.wear.protolayout.ColorBuilders.ColorProp);
-    method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public androidx.wear.protolayout.material.Text.Builder setExcludeFontPadding(boolean);
     method public androidx.wear.protolayout.material.Text.Builder setItalic(boolean);
     method public androidx.wear.protolayout.material.Text.Builder setMaxLines(@IntRange(from=1) int);
     method public androidx.wear.protolayout.material.Text.Builder setModifiers(androidx.wear.protolayout.ModifiersBuilders.Modifiers);
@@ -204,14 +200,12 @@
     method public String? getIconContent();
     method public String getText();
     method public androidx.wear.protolayout.DimensionBuilders.ContainerDimension getWidth();
-    method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public boolean hasExcludeFontPadding();
   }
 
   public static final class TitleChip.Builder {
     ctor public TitleChip.Builder(android.content.Context, String, androidx.wear.protolayout.ModifiersBuilders.Clickable, androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters);
     method public androidx.wear.protolayout.material.TitleChip build();
     method public androidx.wear.protolayout.material.TitleChip.Builder setChipColors(androidx.wear.protolayout.material.ChipColors);
-    method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public androidx.wear.protolayout.material.TitleChip.Builder setExcludeFontPadding(boolean);
     method public androidx.wear.protolayout.material.TitleChip.Builder setHorizontalAlignment(int);
     method public androidx.wear.protolayout.material.TitleChip.Builder setIconContent(String);
     method public androidx.wear.protolayout.material.TitleChip.Builder setWidth(androidx.wear.protolayout.DimensionBuilders.ContainerDimension);
diff --git a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/TestCasesGenerator.java b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/TestCasesGenerator.java
index 04eb408..282fe24 100644
--- a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/TestCasesGenerator.java
+++ b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/TestCasesGenerator.java
@@ -31,7 +31,6 @@
 import androidx.wear.protolayout.LayoutElementBuilders;
 import androidx.wear.protolayout.LayoutElementBuilders.Box;
 import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement;
-import androidx.wear.protolayout.LayoutElementBuilders.Row;
 import androidx.wear.protolayout.ModifiersBuilders.Background;
 import androidx.wear.protolayout.ModifiersBuilders.Clickable;
 import androidx.wear.protolayout.ModifiersBuilders.Modifiers;
@@ -54,6 +53,7 @@
      * as it should point on the same size independent image.
      */
     @NonNull
+    @SuppressWarnings("deprecation")
     static Map<String, LayoutElement> generateTestCases(
             @NonNull Context context,
             @NonNull DeviceParametersBuilders.DeviceParameters deviceParameters,
@@ -210,78 +210,61 @@
         testCases.put(
                 "compactchip_default_len2_golden" + goldenSuffix,
                 new CompactChip.Builder(context, "Ab", clickable, deviceParameters)
-                        .setExcludeFontPadding(true)
                         .build());
         testCases.put(
                 "compactchip_default_len5_golden" + goldenSuffix,
                 new CompactChip.Builder(context, "Abcde", clickable, deviceParameters)
-                        .setExcludeFontPadding(true)
                         .build());
         testCases.put(
                 "compactchip_default_len9_golden" + goldenSuffix,
                 new CompactChip.Builder(context, "Abcdefghi", clickable, deviceParameters)
-                        .setExcludeFontPadding(true)
                         .build());
         testCases.put(
                 "compactchip_default_toolong_golden" + goldenSuffix,
                 new CompactChip.Builder(
                                 context, "AbcdefghiEXTRAEXTRAEXTRA", clickable, deviceParameters)
-                        .setExcludeFontPadding(true)
                         .build());
         testCases.put(
                 "compactchip_custom_default_golden" + goldenSuffix,
                 new CompactChip.Builder(context, "Action", clickable, deviceParameters)
                         .setChipColors(new ChipColors(Color.YELLOW, Color.BLACK))
-                        .setExcludeFontPadding(true)
-                        .build());
-        testCases.put(
-                "compactchip_includepadding_default_golden" + goldenSuffix,
-                new CompactChip.Builder(context, "Action", clickable, deviceParameters)
-                        .setExcludeFontPadding(false)
                         .build());
         testCases.put(
                 "compactchip_icon_default_golden" + goldenSuffix,
                 new CompactChip.Builder(context, "Action", clickable, deviceParameters)
                         .setIconContent(ICON_ID_SMALL)
-                        .setExcludeFontPadding(true)
                         .build());
         testCases.put(
                 "compactchip_icon_toolong_golden" + goldenSuffix,
                 new CompactChip.Builder(
                                 context, "AbcdefghiEXTRAEXTRAEXTRA", clickable, deviceParameters)
                         .setIconContent(ICON_ID_SMALL)
-                        .setExcludeFontPadding(true)
                         .build());
         testCases.put(
                 "compactchip_icon_len2_golden" + goldenSuffix,
                 new CompactChip.Builder(context, "Ab", clickable, deviceParameters)
                         .setIconContent(ICON_ID_SMALL)
-                        .setExcludeFontPadding(true)
                         .build());
         testCases.put(
                 "compactchip_icon_custom_golden" + goldenSuffix,
                 new CompactChip.Builder(context, "Action", clickable, deviceParameters)
                         .setIconContent(ICON_ID_SMALL)
-                        .setExcludeFontPadding(true)
                         .setChipColors(new ChipColors(Color.YELLOW, Color.BLACK))
                         .build());
 
         testCases.put(
                 "titlechip_default_golden" + goldenSuffix,
                 new TitleChip.Builder(context, largeChipText, clickable, deviceParameters)
-                        .setExcludeFontPadding(true)
                         .build());
         testCases.put(
                 "titlechip_default_texttoolong_golden" + goldenSuffix,
                 new TitleChip.Builder(context, "abcdeabcdeabcdeEXTRA", clickable, deviceParameters)
-                        .setExcludeFontPadding(true)
                         .build());
         testCases.put(
                 "titlechip_leftalign_secondary_default_golden" + goldenSuffix,
                 new TitleChip.Builder(context, largeChipText, clickable, deviceParameters)
                         .setHorizontalAlignment(HORIZONTAL_ALIGN_START)
                         .setChipColors(ChipDefaults.TITLE_SECONDARY_COLORS)
-                        .setExcludeFontPadding(true)
                         .build());
         testCases.put(
                 "titlechip_centered_custom_150_secondary_default_golden" + goldenSuffix,
@@ -289,23 +272,15 @@
                         .setHorizontalAlignment(HORIZONTAL_ALIGN_CENTER)
                         .setChipColors(new ChipColors(Color.YELLOW, Color.BLUE))
                         .setWidth(150)
-                        .setExcludeFontPadding(true)
-                        .build());
-        testCases.put(
-                "titlechip_includepadding_default_golden" + goldenSuffix,
-                new TitleChip.Builder(context, largeChipText, clickable, deviceParameters)
-                        .setExcludeFontPadding(false)
                         .build());
         testCases.put(
                 "titlechip_icon_default_golden" + goldenSuffix,
                 new TitleChip.Builder(context, largeChipText, clickable, deviceParameters)
-                        .setExcludeFontPadding(true)
                         .setIconContent(ICON_ID)
                         .build());
         testCases.put(
                 "titlechip_icon_default_texttoolong_golden" + goldenSuffix,
                 new TitleChip.Builder(context, "abcdeabcdeabcdeEXTRA", clickable, deviceParameters)
-                        .setExcludeFontPadding(true)
                         .setIconContent(ICON_ID)
                         .build());
         testCases.put(
@@ -313,7 +288,6 @@
                 new TitleChip.Builder(context, largeChipText, clickable, deviceParameters)
                         .setChipColors(new ChipColors(Color.YELLOW, Color.BLUE))
                         .setWidth(150)
-                        .setExcludeFontPadding(true)
                         .setIconContent(ICON_ID)
                         .build());
 
@@ -348,31 +322,8 @@
                         .build());
 
         testCases.put(
-                "default_text_golden" + goldenSuffix, new Text.Builder(context, "Testing").build());
-        testCases.put(
-                "excluded_padding_text_golden" + goldenSuffix,
-                new Row.Builder()
-                        .addContent(
-                                new Box.Builder()
-                                        .setModifiers(buildBackgroundColorModifier(Color.YELLOW))
-                                        .addContent(
-                                                new Text.Builder(context, "Inc padd ")
-                                                        .setExcludeFontPadding(false)
-                                                        .setTypography(
-                                                                Typography.TYPOGRAPHY_CAPTION1)
-                                                        .build())
-                                        .build())
-                        .addContent(
-                                new Box.Builder()
-                                        .setModifiers(buildBackgroundColorModifier(Color.CYAN))
-                                        .addContent(
-                                                new Text.Builder(context, "Excl padd")
-                                                        .setExcludeFontPadding(true)
-                                                        .setTypography(
-                                                                Typography.TYPOGRAPHY_CAPTION1)
-                                                        .build())
-                                        .build())
-                        .build());
+                "default_text_golden" + goldenSuffix,
+                new Text.Builder(context, "Testing").build());
         testCases.put(
                 "custom_text_golden" + goldenSuffix,
                 new Text.Builder(context, "Testing text.")
@@ -384,6 +335,47 @@
         testCases.put(
                 "overflow_text_golden" + goldenSuffix,
                 new Text.Builder(context, "abcdeabcdeabcde").build());
+        testCases.put(
+                "overflow_ellipsize_maxlines_notreached" + goldenSuffix,
+                new Box.Builder()
+                        .setWidth(dp(100))
+                        .setHeight(dp(42))
+                        .setModifiers(buildBackgroundColorModifier(Color.YELLOW))
+                        .addContent(
+                                new Text.Builder(
+                                        context,
+                                        "Very long text that won't fit in its parent box so it"
+                                                + "needs to be ellipsized correctly before its "
+                                                + "last line")
+                                        // Line height = 20sp
+                                        .setTypography(Typography.TYPOGRAPHY_BODY1)
+                                        .setOverflow(LayoutElementBuilders.TEXT_OVERFLOW_ELLIPSIZE)
+                                        .setMultilineAlignment(
+                                                LayoutElementBuilders.TEXT_ALIGN_START)
+                                        .setMaxLines(6)
+                                        .build())
+                        .build());
+        testCases.put(
+                "overflow_ellipsize_end_maxlines_notreached" + goldenSuffix,
+                new Box.Builder()
+                        .setWidth(dp(100))
+                        .setHeight(dp(42))
+                        .setModifiers(buildBackgroundColorModifier(Color.YELLOW))
+                        .addContent(
+                                new Text.Builder(
+                                        context,
+                                        "Very long text that won't fit in its parent box so it"
+                                                + "needs to be ellipsized correctly before its "
+                                                + "last line")
+                                        // Line height = 20sp
+                                        .setTypography(Typography.TYPOGRAPHY_BODY1)
+                                        .setOverflow(
+                                                LayoutElementBuilders.TEXT_OVERFLOW_ELLIPSIZE_END)
+                                        .setMultilineAlignment(
+                                                LayoutElementBuilders.TEXT_ALIGN_START)
+                                        .setMaxLines(6)
+                                        .build())
+                        .build());
 
         return testCases;
     }
diff --git a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/TestCasesGenerator.java b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/TestCasesGenerator.java
index 7c7dc9d..a515831 100644
--- a/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/TestCasesGenerator.java
+++ b/wear/protolayout/protolayout-material/src/androidTest/java/androidx/wear/protolayout/material/layouts/TestCasesGenerator.java
@@ -75,12 +75,9 @@
         HashMap<String, LayoutElement> testCases = new HashMap<>();
 
         TitleChip content =
-                new TitleChip.Builder(context, "Action", clickable, deviceParameters)
-                        .setExcludeFontPadding(true)
-                        .build();
+                new TitleChip.Builder(context, "Action", clickable, deviceParameters).build();
         CompactChip.Builder primaryChipBuilder =
-                new CompactChip.Builder(context, "Action", clickable, deviceParameters)
-                        .setExcludeFontPadding(true);
+                new CompactChip.Builder(context, "Action", clickable, deviceParameters);
 
         testCases.put(
                 "default_empty_primarychiplayout_golden" + goldenSuffix,
@@ -96,7 +93,6 @@
                                                 "Too_long_textToo_long_textToo_long_text",
                                                 clickable,
                                                 deviceParameters)
-                                        .setExcludeFontPadding(true)
                                         .build())
                         .build());
         testCases.put(
@@ -166,8 +162,7 @@
                         .build());
 
         primaryChipBuilder =
-                new CompactChip.Builder(context, "Action", clickable, deviceParameters)
-                        .setExcludeFontPadding(true);
+                new CompactChip.Builder(context, "Action", clickable, deviceParameters);
         testCases.put(
                 "coloredbox_1_chip_columnslayout_golden" + goldenSuffix,
                 new PrimaryLayout.Builder(deviceParameters)
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Button.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Button.java
index d35c3db..56ef3bc 100644
--- a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Button.java
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Button.java
@@ -40,6 +40,7 @@
 import androidx.annotation.Dimension;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.OptIn;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.RestrictTo.Scope;
 import androidx.wear.protolayout.ColorBuilders.ColorProp;
@@ -51,6 +52,7 @@
 import androidx.wear.protolayout.ModifiersBuilders.Clickable;
 import androidx.wear.protolayout.TypeBuilders.StringProp;
 import androidx.wear.protolayout.expression.Fingerprint;
+import androidx.wear.protolayout.expression.ProtoLayoutExperimental;
 import androidx.wear.protolayout.material.Typography.TypographyName;
 import androidx.wear.protolayout.materialcore.Button.Builder.ButtonType;
 import androidx.wear.protolayout.proto.LayoutElementProto;
@@ -285,6 +287,7 @@
         }
 
         @NonNull
+        @OptIn(markerClass = ProtoLayoutExperimental.class)
         private LayoutElement getCorrectContent() {
             LayoutElement.Builder content;
             switch (mType) {
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Chip.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Chip.java
index 67ddcc5..c0854ac 100644
--- a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Chip.java
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Chip.java
@@ -111,7 +111,6 @@
         @HorizontalAlignment private int mHorizontalAlign = HORIZONTAL_ALIGN_UNDEFINED;
         @TypographyName private int mPrimaryLabelTypography;
         private boolean mIsScalable = true;
-        private boolean mIsFontPaddingExcluded = false;
         private int mMaxLines = 0; // 0 indicates that is not set.
         @NonNull private final androidx.wear.protolayout.materialcore.Chip.Builder mCoreBuilder;
 
@@ -240,23 +239,6 @@
         }
 
         /**
-         * Sets whether the font padding for the primary label is excluded.
-         *
-         * <p>It should be used for creating {@code CompactChip} and {@code TitleChip} to make the
-         * label vertically aligned. Shouldn't be used if there is anything else in chip besides
-         * primary label.
-         *
-         * @see Text.Builder#setExcludeFontPadding
-         */
-        @NonNull
-        @ProtoLayoutExperimental
-        @SuppressWarnings("MissingGetterMatchingBuilder")
-        Builder setPrimaryLabelExcludeFontPadding(boolean excluded) {
-            this.mIsFontPaddingExcluded = excluded;
-            return this;
-        }
-
-        /**
          * Sets the secondary label for the {@link Chip}. Any previously added custom content will
          * be overridden. If secondary label is set, primary label must be set too with {@link
          * #setPrimaryLabelContent}.
@@ -372,6 +354,7 @@
         }
 
         @OptIn(markerClass = ProtoLayoutExperimental.class)
+        @SuppressWarnings("deprecation")
         private void setCorrectContent() {
             Text mainTextElement =
                     new Text.Builder(mContext, checkNotNull(mPrimaryLabel))
@@ -381,7 +364,6 @@
                             .setOverflow(LayoutElementBuilders.TEXT_OVERFLOW_ELLIPSIZE_END)
                             .setMultilineAlignment(LayoutElementBuilders.TEXT_ALIGN_START)
                             .setIsScalable(mIsScalable)
-                            .setExcludeFontPadding(mIsFontPaddingExcluded)
                             .build();
 
             mCoreBuilder.setPrimaryLabelContent(mainTextElement);
@@ -543,13 +525,6 @@
         return mElement.getMetadataTag();
     }
 
-    /** Returns whether the font padding for the primary label is excluded. */
-    @ProtoLayoutExperimental
-    boolean hasPrimaryLabelExcludeFontPadding() {
-        Text primaryLabel = getPrimaryLabelContentObject();
-        return primaryLabel != null && primaryLabel.hasExcludeFontPadding();
-    }
-
     /**
      * Returns Chip object from the given LayoutElement (e.g. one retrieved from a container's
      * content with {@code container.getContents().get(index)}) if that element can be converted to
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/CompactChip.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/CompactChip.java
index 436aaad..9787fa6 100644
--- a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/CompactChip.java
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/CompactChip.java
@@ -83,7 +83,6 @@
         @NonNull private final Clickable mClickable;
         @NonNull private final DeviceParameters mDeviceParameters;
         @NonNull private ChipColors mChipColors = COMPACT_PRIMARY_COLORS;
-        private boolean mIsFontPaddingExcluded = false;
         @Nullable private String mIconResourceId = null;
 
         /**
@@ -119,25 +118,9 @@
         }
 
         /**
-         * Sets whether the font padding is excluded or not. If not set, default to false, meaning
-         * that text will have font padding included.
-         *
-         * <p>Setting this to {@code true} will perfectly align the text label.
-         */
-        @NonNull
-        @ProtoLayoutExperimental
-        @SuppressWarnings("MissingGetterMatchingBuilder")
-        public Builder setExcludeFontPadding(boolean excluded) {
-            this.mIsFontPaddingExcluded = excluded;
-            return this;
-        }
-
-        /**
          * Sets the icon for the {@link CompactChip}. Provided icon will be tinted to the given
          * content color from {@link ChipColors}. This icon should be image with chosen alpha
          * channel that can be tinted.
-         *
-         * <p>It is highly recommended to use it with {@link #setExcludeFontPadding} set to true.
          */
         @NonNull
         public Builder setIconContent(@NonNull String imageResourceId) {
@@ -161,7 +144,6 @@
                             .setHorizontalPadding(COMPACT_HORIZONTAL_PADDING)
                             .setPrimaryLabelContent(mText)
                             .setPrimaryLabelTypography(Typography.TYPOGRAPHY_CAPTION1)
-                            .setPrimaryLabelExcludeFontPadding(mIsFontPaddingExcluded)
                             .setIsPrimaryLabelScalable(false);
 
             if (mIconResourceId != null) {
@@ -221,12 +203,6 @@
         return coreChip == null ? null : new CompactChip(new Chip(coreChip));
     }
 
-    /** Returns whether the font padding for the primary label is excluded. */
-    @ProtoLayoutExperimental
-    public boolean hasExcludeFontPadding() {
-        return mElement.hasPrimaryLabelExcludeFontPadding();
-    }
-
     @RestrictTo(Scope.LIBRARY_GROUP)
     @NonNull
     @Override
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Text.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Text.java
index 5f0ef96..dd110c0 100644
--- a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Text.java
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/Text.java
@@ -30,6 +30,7 @@
 import androidx.annotation.IntRange;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.OptIn;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.RestrictTo.Scope;
 import androidx.wear.protolayout.ColorBuilders.ColorProp;
@@ -91,6 +92,8 @@
         @Nullable private Integer mCustomWeight = null;
 
         @NonNull
+        @OptIn(markerClass = ProtoLayoutExperimental.class)
+        @SuppressWarnings("deprecation")
         private final LayoutElementBuilders.Text.Builder mElementBuilder =
                 new LayoutElementBuilders.Text.Builder()
                         .setMaxLines(1)
@@ -221,24 +224,6 @@
             return this;
         }
 
-        /**
-         * Sets whether the {@link Text} excludes extra top and bottom padding above the normal
-         * ascent and descent. The default is false.
-         */
-        // TODO(b/252767963): Coordinate the transition of the default from false->true along with
-        // other impacted UI Libraries - needs care as will have an impact on layout and needs to be
-        // communicated clearly.
-        @NonNull
-        @ProtoLayoutExperimental
-        @SuppressWarnings("MissingGetterMatchingBuilder")
-        public Builder setExcludeFontPadding(boolean excludeFontPadding) {
-            this.mElementBuilder.setAndroidTextStyle(
-                    new LayoutElementBuilders.AndroidTextStyle.Builder()
-                            .setExcludeFontPadding(excludeFontPadding)
-                            .build());
-            return this;
-        }
-
         /** Constructs and returns {@link Text} with the provided content and look. */
         @NonNull
         @Override
@@ -321,15 +306,6 @@
     }
 
     /**
-     * Returns whether the Text has extra top and bottom padding above the normal ascent and descent
-     * excluded.
-     */
-    @ProtoLayoutExperimental
-    public boolean hasExcludeFontPadding() {
-        return checkNotNull(mText.getAndroidTextStyle()).getExcludeFontPadding();
-    }
-
-    /**
      * Returns Material Text object from the given LayoutElement (e.g. one retrieved from a
      * container's content with {@code container.getContents().get(index)}) if that element can be
      * converted to Material Text. Otherwise, it will return null.
diff --git a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/TitleChip.java b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/TitleChip.java
index e6e355c..25de00b 100644
--- a/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/TitleChip.java
+++ b/wear/protolayout/protolayout-material/src/main/java/androidx/wear/protolayout/material/TitleChip.java
@@ -93,7 +93,6 @@
         // Indicates that the width isn't set, so it will be automatically set by Chip.Builder
         // constructor.
         @Nullable private ContainerDimension mWidth = null;
-        private boolean mIsFontPaddingExcluded = false;
         @Nullable private String mIconResourceId = null;
 
         /**
@@ -157,25 +156,9 @@
         }
 
         /**
-         * Sets whether the font padding is excluded or not. If not set, default to false, meaning
-         * that text will have font padding included.
-         *
-         * <p>Setting this to {@code true} will perfectly align the text label.
-         */
-        @NonNull
-        @ProtoLayoutExperimental
-        @SuppressWarnings("MissingGetterMatchingBuilder")
-        public Builder setExcludeFontPadding(boolean excluded) {
-            this.mIsFontPaddingExcluded = excluded;
-            return this;
-        }
-
-        /**
          * Sets the icon for the {@link TitleChip}. Provided icon will be tinted to the given
          * content color from {@link ChipColors}. This icon should be image with chosen alpha
          * channel that can be tinted.
-         *
-         * <p>It is highly recommended to use it with {@link #setExcludeFontPadding} set to true.
          */
         @NonNull
         public Builder setIconContent(@NonNull String imageResourceId) {
@@ -197,7 +180,6 @@
                             .setHorizontalPadding(TITLE_HORIZONTAL_PADDING)
                             .setPrimaryLabelContent(mText)
                             .setPrimaryLabelTypography(Typography.TYPOGRAPHY_TITLE2)
-                            .setPrimaryLabelExcludeFontPadding(mIsFontPaddingExcluded)
                             .setIsPrimaryLabelScalable(false);
 
             if (mWidth != null) {
@@ -273,12 +255,6 @@
         return coreChip == null ? null : new TitleChip(new Chip(coreChip));
     }
 
-    /** Returns whether the font padding for the primary label is excluded. */
-    @ProtoLayoutExperimental
-    public boolean hasExcludeFontPadding() {
-        return mElement.hasPrimaryLabelExcludeFontPadding();
-    }
-
     @RestrictTo(Scope.LIBRARY_GROUP)
     @NonNull
     @Override
diff --git a/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/TextTest.java b/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/TextTest.java
index ea6f7b9..fbf9866 100644
--- a/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/TextTest.java
+++ b/wear/protolayout/protolayout-material/src/test/java/androidx/wear/protolayout/material/TextTest.java
@@ -21,7 +21,6 @@
 import static androidx.wear.protolayout.LayoutElementBuilders.FONT_WEIGHT_MEDIUM;
 import static androidx.wear.protolayout.LayoutElementBuilders.FONT_WEIGHT_NORMAL;
 import static androidx.wear.protolayout.LayoutElementBuilders.TEXT_ALIGN_END;
-import static androidx.wear.protolayout.LayoutElementBuilders.TEXT_OVERFLOW_ELLIPSIZE_END;
 import static androidx.wear.protolayout.material.Typography.TYPOGRAPHY_BODY1;
 import static androidx.wear.protolayout.material.Typography.TYPOGRAPHY_CAPTION2;
 import static androidx.wear.protolayout.material.Typography.TYPOGRAPHY_TITLE1;
@@ -130,6 +129,7 @@
 
     @Test
     @ProtoLayoutExperimental
+    @SuppressWarnings("deprecation")
     public void testText() {
         String textContent = "Testing text.";
         Modifiers modifiers =
@@ -145,10 +145,9 @@
                         .setUnderline(true)
                         .setMaxLines(2)
                         .setModifiers(modifiers)
-                        .setOverflow(TEXT_OVERFLOW_ELLIPSIZE_END)
+                        .setOverflow(LayoutElementBuilders.TEXT_OVERFLOW_ELLIPSIZE_END)
                         .setMultilineAlignment(TEXT_ALIGN_END)
                         .setWeight(FONT_WEIGHT_BOLD)
-                        .setExcludeFontPadding(true)
                         .build();
 
         FontStyle expectedFontStyle =
@@ -201,6 +200,7 @@
     }
 
     @ProtoLayoutExperimental
+    @SuppressWarnings("deprecation")
     private void assertTextIsEqual(
             Text actualText,
             String expectedTextContent,
@@ -210,12 +210,12 @@
         assertThat(actualText.getFontStyle().toProto()).isEqualTo(expectedFontStyle.toProto());
         assertThat(actualText.getText().getValue()).isEqualTo(expectedTextContent);
         assertThat(actualText.getColor().getArgb()).isEqualTo(expectedColor);
-        assertThat(actualText.getOverflow()).isEqualTo(TEXT_OVERFLOW_ELLIPSIZE_END);
+        assertThat(actualText.getOverflow())
+                .isEqualTo(LayoutElementBuilders.TEXT_OVERFLOW_ELLIPSIZE_END);
         assertThat(actualText.getMultilineAlignment()).isEqualTo(TEXT_ALIGN_END);
         assertThat(actualText.getMaxLines()).isEqualTo(2);
         assertThat(actualText.getLineHeight())
                 .isEqualTo(getLineHeightForTypography(TYPOGRAPHY_TITLE1).getValue());
-        assertThat(actualText.hasExcludeFontPadding()).isTrue();
     }
 
     private void assertFontStyle(
diff --git a/wear/protolayout/protolayout-proto/src/main/proto/layout.proto b/wear/protolayout/protolayout-proto/src/main/proto/layout.proto
index 96fc634..cd2caa6 100644
--- a/wear/protolayout/protolayout-proto/src/main/proto/layout.proto
+++ b/wear/protolayout/protolayout-proto/src/main/proto/layout.proto
@@ -151,6 +151,7 @@
 
   // Truncate the text to fit in the Text element's bounds, but add an ellipsis
   // (i.e. ...) to the end of the text if it has been truncated.
+  // @deprecated Use TEXT_OVERFLOW_ELLIPSIZE instead.
   TEXT_OVERFLOW_ELLIPSIZE_END = 2;
 
   // Enable marquee animation for texts that don't fit inside the Text element.
@@ -167,6 +168,8 @@
   // Note that, when this is used, the parent of the Text element this
   // corresponds to shouldn't have its width and height set to wrapped, as it
   // can lead to unexpected results.
+  // <p>Note that, on SpanText, this will behave exactly the same way as
+  // TEXT_OVERFLOW_ELLIPSIZE_END.
   TEXT_OVERFLOW_ELLIPSIZE = 4;
 }
 
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
index 993dd38..baf27ab 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflater.java
@@ -69,6 +69,7 @@
 import android.view.ViewGroup.LayoutParams;
 import android.view.ViewOutlineProvider;
 import android.view.ViewParent;
+import android.view.ViewTreeObserver.OnPreDrawListener;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.AnimationSet;
 import android.view.animation.TranslateAnimation;
@@ -1873,13 +1874,12 @@
                 // A null TruncateAt disables adding an ellipsis.
                 return null;
             case TEXT_OVERFLOW_ELLIPSIZE_END:
+            case TEXT_OVERFLOW_ELLIPSIZE:
                 return TruncateAt.END;
             case TEXT_OVERFLOW_MARQUEE:
                 return TruncateAt.MARQUEE;
             case TEXT_OVERFLOW_UNDEFINED:
             case UNRECOGNIZED:
-                // TODO(b/302531877): Implement ellipsize.
-            case TEXT_OVERFLOW_ELLIPSIZE:
                 return TEXT_OVERFLOW_DEFAULT;
         }
 
@@ -2550,7 +2550,14 @@
         } else {
             textView.setMaxLines(TEXT_MAX_LINES_DEFAULT);
         }
-        applyTextOverflow(textView, text.getOverflow(), text.getMarqueeParameters());
+
+        TextOverflowProp overflow = text.getOverflow();
+        applyTextOverflow(textView, overflow, text.getMarqueeParameters());
+
+        if (overflow.getValue() == TextOverflow.TEXT_OVERFLOW_ELLIPSIZE
+                && !text.getText().hasDynamicValue()) {
+            adjustMaxLinesForEllipsize(textView);
+        }
 
         // Text auto size is not supported for dynamic text.
         boolean isAutoSizeAllowed = !(text.hasText() && text.getText().hasDynamicValue());
@@ -2642,6 +2649,52 @@
     }
 
     /**
+     * Sorts out what maxLines should be if the text could possibly be truncated before maxLines is
+     * reached.
+     *
+     * <p>Should be only called for the {@link TextOverflow#TEXT_OVERFLOW_ELLIPSIZE} option which
+     * ellipsizes the text even before the last line, if there's no space for all lines. This is
+     * different than what TEXT_OVERFLOW_ELLIPSIZE_END does, as that option just ellipsizes the last
+     * line of text.
+     */
+    private void adjustMaxLinesForEllipsize(@NonNull TextView textView) {
+        textView
+                .getViewTreeObserver()
+                .addOnPreDrawListener(
+                        new OnPreDrawListener() {
+                            @Override
+                            public boolean onPreDraw() {
+                                ViewParent maybeParent = textView.getParent();
+                                if (!(maybeParent instanceof View)) {
+                                    Log.d(
+                                            TAG,
+                                            "Couldn't adjust max lines for ellipsizing as"
+                                                    + "there's no View/ViewGroup parent.");
+                                    return false;
+                                }
+
+                                textView.getViewTreeObserver().removeOnPreDrawListener(this);
+
+                                View parent = (View) maybeParent;
+                                int availableHeight = parent.getHeight();
+                                int oneLineHeight = textView.getLineHeight();
+                                // This is what was set in proto, we shouldn't exceed it.
+                                int maxMaxLines = textView.getMaxLines();
+                                // Avoid having maxLines as 0 in case the space is really tight.
+                                int availableLines = max(availableHeight / oneLineHeight, 1);
+
+                                // Update only if changed.
+                                if (availableLines < maxMaxLines) {
+                                    textView.setMaxLines(availableLines);
+                                }
+
+                                // Cancel the current drawing pass.
+                                return false;
+                            }
+                        });
+    }
+
+    /**
      * Sets whether the padding is included or not. If font padding is not included, sets the
      * correct padding to the TextView to avoid clipping taller languages.
      */
diff --git a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/dynamicdata/ProtoLayoutDynamicDataPipelineTest.java b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/dynamicdata/ProtoLayoutDynamicDataPipelineTest.java
index 3c7e0b8..151dabf 100644
--- a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/dynamicdata/ProtoLayoutDynamicDataPipelineTest.java
+++ b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/dynamicdata/ProtoLayoutDynamicDataPipelineTest.java
@@ -52,6 +52,7 @@
 import androidx.wear.protolayout.expression.proto.AnimationParameterProto.RepeatMode;
 import androidx.wear.protolayout.expression.proto.AnimationParameterProto.Repeatable;
 import androidx.wear.protolayout.expression.proto.DynamicDataProto.DynamicDataValue;
+import androidx.wear.protolayout.expression.proto.DynamicProto;
 import androidx.wear.protolayout.expression.proto.DynamicProto.AnimatableDynamicColor;
 import androidx.wear.protolayout.expression.proto.DynamicProto.AnimatableDynamicFloat;
 import androidx.wear.protolayout.expression.proto.DynamicProto.AnimatableFixedColor;
@@ -855,7 +856,12 @@
                         .build();
         DynamicInt32 dynamicInt32 =
                 DynamicInt32.newBuilder()
-                        .setFloatToInt(FloatToInt32Op.newBuilder().setInput(dynamicFloat).build())
+                        .setFloatToInt(
+                                FloatToInt32Op.newBuilder()
+                                        .setRoundMode(
+                                                DynamicProto.FloatToInt32RoundMode.ROUND_MODE_ROUND)
+                                        .setInput(dynamicFloat)
+                                        .build())
                         .build();
         DynamicString dynamicString =
                 DynamicString.newBuilder()
diff --git a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java
index 4fa4807..950e73f 100644
--- a/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java
+++ b/wear/protolayout/protolayout-renderer/src/test/java/androidx/wear/protolayout/renderer/inflater/ProtoLayoutInflaterTest.java
@@ -2765,6 +2765,70 @@
     }
 
     @Test
+    public void inflate_textView_ellipsize() {
+        String textContents = "Text that is very large so it will go to many lines";
+        Text.Builder text1 =
+                Text.newBuilder()
+                        .setLineHeight(sp(16))
+                        .setText(string(textContents))
+                        .setFontStyle(FontStyle.newBuilder().addSize(sp(16)))
+                        .setMaxLines(Int32Prop.newBuilder().setValue(6))
+                        .setOverflow(
+                                TextOverflowProp.newBuilder().setValue(
+                                        TextOverflow.TEXT_OVERFLOW_ELLIPSIZE));
+        Layout layout1 =
+                fingerprintedLayout(
+                        LayoutElement.newBuilder()
+                                .setBox(buildFixedSizeBoxWIthText(text1)).build());
+
+        Text.Builder text2 =
+                Text.newBuilder()
+                        .setText(string(textContents))
+                        // Diff
+                        .setLineHeight(sp(4))
+                        .setFontStyle(FontStyle.newBuilder().addSize(sp(4)))
+                        .setMaxLines(Int32Prop.newBuilder().setValue(6))
+                        .setOverflow(
+                                TextOverflowProp.newBuilder().setValue(
+                                        TextOverflow.TEXT_OVERFLOW_ELLIPSIZE));
+        Layout layout2 =
+                fingerprintedLayout(
+                        LayoutElement.newBuilder()
+                                .setBox(buildFixedSizeBoxWIthText(text2)).build());
+
+        // Initial layout.
+        Renderer renderer = renderer(layout1);
+        ViewGroup inflatedViewParent = renderer.inflate();
+        TextView textView1 = (TextView) ((ViewGroup) inflatedViewParent
+                .getChildAt(0)).getChildAt(0);
+
+        // Apply the mutation.
+        ViewGroupMutation mutation =
+                renderer.computeMutation(getRenderedMetadata(inflatedViewParent), layout2);
+        assertThat(mutation).isNotNull();
+        assertThat(mutation.isNoOp()).isFalse();
+        boolean mutationResult = renderer.applyMutation(inflatedViewParent, mutation);
+        assertThat(mutationResult).isTrue();
+
+        // This contains layout after the mutation.
+        TextView textView2 = (TextView) ((ViewGroup) inflatedViewParent
+                .getChildAt(0)).getChildAt(0);
+
+        expect.that(textView1.getEllipsize()).isEqualTo(TruncateAt.END);
+        expect.that(textView1.getMaxLines()).isEqualTo(2);
+
+        expect.that(textView2.getEllipsize()).isEqualTo(TruncateAt.END);
+        expect.that(textView2.getMaxLines()).isEqualTo(3);
+    }
+
+    private static Box.Builder buildFixedSizeBoxWIthText(Text.Builder content) {
+        return Box.newBuilder()
+                .setWidth(ContainerDimension.newBuilder().setLinearDimension(dp(100)))
+                .setHeight(ContainerDimension.newBuilder().setLinearDimension(dp(120)))
+                .addContents(LayoutElement.newBuilder().setText(content));
+    }
+
+    @Test
     public void inflate_textView_marquee_animationsDisabled() {
         String textContents = "Marquee Animation";
         LayoutElement root =
diff --git a/wear/protolayout/protolayout/api/current.txt b/wear/protolayout/protolayout/api/current.txt
index e3f0976..2a416d7 100644
--- a/wear/protolayout/protolayout/api/current.txt
+++ b/wear/protolayout/protolayout/api/current.txt
@@ -127,9 +127,8 @@
   }
 
   public static final class ColorBuilders.ColorStop.Builder {
-    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public ColorBuilders.ColorStop.Builder(androidx.wear.protolayout.ColorBuilders.ColorProp);
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public ColorBuilders.ColorStop.Builder(androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.TypeBuilders.FloatProp);
     method public androidx.wear.protolayout.ColorBuilders.ColorStop build();
-    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public androidx.wear.protolayout.ColorBuilders.ColorStop.Builder setOffset(androidx.wear.protolayout.TypeBuilders.FloatProp);
   }
 
   @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public static final class ColorBuilders.SweepGradient implements androidx.wear.protolayout.ColorBuilders.Brush {
@@ -139,6 +138,7 @@
   }
 
   public static final class ColorBuilders.SweepGradient.Builder {
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) @java.lang.SafeVarargs public ColorBuilders.SweepGradient.Builder(androidx.wear.protolayout.ColorBuilders.ColorProp!...);
     ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) @java.lang.SafeVarargs public ColorBuilders.SweepGradient.Builder(androidx.wear.protolayout.ColorBuilders.ColorStop!...);
     method public androidx.wear.protolayout.ColorBuilders.SweepGradient build();
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public androidx.wear.protolayout.ColorBuilders.SweepGradient.Builder setEndAngle(androidx.wear.protolayout.DimensionBuilders.DegreesProp);
@@ -359,7 +359,7 @@
     field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final int TEXT_ALIGN_START = 1; // 0x1
     field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final int TEXT_ALIGN_UNDEFINED = 0; // 0x0
     field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public static final int TEXT_OVERFLOW_ELLIPSIZE = 4; // 0x4
-    field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final int TEXT_OVERFLOW_ELLIPSIZE_END = 2; // 0x2
+    field @Deprecated @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final int TEXT_OVERFLOW_ELLIPSIZE_END = 2; // 0x2
     field @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public static final int TEXT_OVERFLOW_MARQUEE = 3; // 0x3
     field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final int TEXT_OVERFLOW_TRUNCATE = 1; // 0x1
     field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final int TEXT_OVERFLOW_UNDEFINED = 0; // 0x0
@@ -369,16 +369,6 @@
     field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final int VERTICAL_ALIGN_UNDEFINED = 0; // 0x0
   }
 
-  @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public static final class LayoutElementBuilders.AndroidTextStyle {
-    method public boolean getExcludeFontPadding();
-  }
-
-  public static final class LayoutElementBuilders.AndroidTextStyle.Builder {
-    ctor public LayoutElementBuilders.AndroidTextStyle.Builder();
-    method public androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle build();
-    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle.Builder setExcludeFontPadding(boolean);
-  }
-
   @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class LayoutElementBuilders.Arc implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
     method public androidx.wear.protolayout.DimensionBuilders.DegreesProp? getAnchorAngle();
     method public androidx.wear.protolayout.LayoutElementBuilders.ArcAnchorTypeProp? getAnchorType();
@@ -725,7 +715,6 @@
   }
 
   @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class LayoutElementBuilders.SpanText implements androidx.wear.protolayout.LayoutElementBuilders.Span {
-    method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle? getAndroidTextStyle();
     method public androidx.wear.protolayout.LayoutElementBuilders.FontStyle? getFontStyle();
     method public androidx.wear.protolayout.ModifiersBuilders.SpanModifiers? getModifiers();
     method public androidx.wear.protolayout.TypeBuilders.StringProp? getText();
@@ -734,7 +723,6 @@
   public static final class LayoutElementBuilders.SpanText.Builder {
     ctor public LayoutElementBuilders.SpanText.Builder();
     method public androidx.wear.protolayout.LayoutElementBuilders.SpanText build();
-    method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.LayoutElementBuilders.SpanText.Builder setAndroidTextStyle(androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle);
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.SpanText.Builder setFontStyle(androidx.wear.protolayout.LayoutElementBuilders.FontStyle);
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.SpanText.Builder setModifiers(androidx.wear.protolayout.ModifiersBuilders.SpanModifiers);
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.SpanText.Builder setText(androidx.wear.protolayout.TypeBuilders.StringProp);
@@ -789,7 +777,6 @@
   }
 
   @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class LayoutElementBuilders.Text implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
-    method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle? getAndroidTextStyle();
     method public androidx.wear.protolayout.LayoutElementBuilders.FontStyle? getFontStyle();
     method public androidx.wear.protolayout.TypeBuilders.StringLayoutConstraint? getLayoutConstraintsForDynamicText();
     method public androidx.wear.protolayout.DimensionBuilders.SpProp? getLineHeight();
@@ -804,7 +791,6 @@
   public static final class LayoutElementBuilders.Text.Builder {
     ctor public LayoutElementBuilders.Text.Builder();
     method public androidx.wear.protolayout.LayoutElementBuilders.Text build();
-    method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.LayoutElementBuilders.Text.Builder setAndroidTextStyle(androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle);
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.Text.Builder setFontStyle(androidx.wear.protolayout.LayoutElementBuilders.FontStyle);
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.LayoutElementBuilders.Text.Builder setLayoutConstraintsForDynamicText(androidx.wear.protolayout.TypeBuilders.StringLayoutConstraint);
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.Text.Builder setLineHeight(androidx.wear.protolayout.DimensionBuilders.SpProp);
diff --git a/wear/protolayout/protolayout/api/restricted_current.txt b/wear/protolayout/protolayout/api/restricted_current.txt
index e3f0976..2a416d7 100644
--- a/wear/protolayout/protolayout/api/restricted_current.txt
+++ b/wear/protolayout/protolayout/api/restricted_current.txt
@@ -127,9 +127,8 @@
   }
 
   public static final class ColorBuilders.ColorStop.Builder {
-    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public ColorBuilders.ColorStop.Builder(androidx.wear.protolayout.ColorBuilders.ColorProp);
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public ColorBuilders.ColorStop.Builder(androidx.wear.protolayout.ColorBuilders.ColorProp, androidx.wear.protolayout.TypeBuilders.FloatProp);
     method public androidx.wear.protolayout.ColorBuilders.ColorStop build();
-    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public androidx.wear.protolayout.ColorBuilders.ColorStop.Builder setOffset(androidx.wear.protolayout.TypeBuilders.FloatProp);
   }
 
   @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public static final class ColorBuilders.SweepGradient implements androidx.wear.protolayout.ColorBuilders.Brush {
@@ -139,6 +138,7 @@
   }
 
   public static final class ColorBuilders.SweepGradient.Builder {
+    ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) @java.lang.SafeVarargs public ColorBuilders.SweepGradient.Builder(androidx.wear.protolayout.ColorBuilders.ColorProp!...);
     ctor @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) @java.lang.SafeVarargs public ColorBuilders.SweepGradient.Builder(androidx.wear.protolayout.ColorBuilders.ColorStop!...);
     method public androidx.wear.protolayout.ColorBuilders.SweepGradient build();
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public androidx.wear.protolayout.ColorBuilders.SweepGradient.Builder setEndAngle(androidx.wear.protolayout.DimensionBuilders.DegreesProp);
@@ -359,7 +359,7 @@
     field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final int TEXT_ALIGN_START = 1; // 0x1
     field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final int TEXT_ALIGN_UNDEFINED = 0; // 0x0
     field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=300) public static final int TEXT_OVERFLOW_ELLIPSIZE = 4; // 0x4
-    field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final int TEXT_OVERFLOW_ELLIPSIZE_END = 2; // 0x2
+    field @Deprecated @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final int TEXT_OVERFLOW_ELLIPSIZE_END = 2; // 0x2
     field @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public static final int TEXT_OVERFLOW_MARQUEE = 3; // 0x3
     field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final int TEXT_OVERFLOW_TRUNCATE = 1; // 0x1
     field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final int TEXT_OVERFLOW_UNDEFINED = 0; // 0x0
@@ -369,16 +369,6 @@
     field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final int VERTICAL_ALIGN_UNDEFINED = 0; // 0x0
   }
 
-  @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public static final class LayoutElementBuilders.AndroidTextStyle {
-    method public boolean getExcludeFontPadding();
-  }
-
-  public static final class LayoutElementBuilders.AndroidTextStyle.Builder {
-    ctor public LayoutElementBuilders.AndroidTextStyle.Builder();
-    method public androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle build();
-    method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle.Builder setExcludeFontPadding(boolean);
-  }
-
   @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class LayoutElementBuilders.Arc implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
     method public androidx.wear.protolayout.DimensionBuilders.DegreesProp? getAnchorAngle();
     method public androidx.wear.protolayout.LayoutElementBuilders.ArcAnchorTypeProp? getAnchorType();
@@ -725,7 +715,6 @@
   }
 
   @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class LayoutElementBuilders.SpanText implements androidx.wear.protolayout.LayoutElementBuilders.Span {
-    method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle? getAndroidTextStyle();
     method public androidx.wear.protolayout.LayoutElementBuilders.FontStyle? getFontStyle();
     method public androidx.wear.protolayout.ModifiersBuilders.SpanModifiers? getModifiers();
     method public androidx.wear.protolayout.TypeBuilders.StringProp? getText();
@@ -734,7 +723,6 @@
   public static final class LayoutElementBuilders.SpanText.Builder {
     ctor public LayoutElementBuilders.SpanText.Builder();
     method public androidx.wear.protolayout.LayoutElementBuilders.SpanText build();
-    method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.LayoutElementBuilders.SpanText.Builder setAndroidTextStyle(androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle);
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.SpanText.Builder setFontStyle(androidx.wear.protolayout.LayoutElementBuilders.FontStyle);
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.SpanText.Builder setModifiers(androidx.wear.protolayout.ModifiersBuilders.SpanModifiers);
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.SpanText.Builder setText(androidx.wear.protolayout.TypeBuilders.StringProp);
@@ -789,7 +777,6 @@
   }
 
   @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public static final class LayoutElementBuilders.Text implements androidx.wear.protolayout.LayoutElementBuilders.LayoutElement {
-    method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle? getAndroidTextStyle();
     method public androidx.wear.protolayout.LayoutElementBuilders.FontStyle? getFontStyle();
     method public androidx.wear.protolayout.TypeBuilders.StringLayoutConstraint? getLayoutConstraintsForDynamicText();
     method public androidx.wear.protolayout.DimensionBuilders.SpProp? getLineHeight();
@@ -804,7 +791,6 @@
   public static final class LayoutElementBuilders.Text.Builder {
     ctor public LayoutElementBuilders.Text.Builder();
     method public androidx.wear.protolayout.LayoutElementBuilders.Text build();
-    method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.LayoutElementBuilders.Text.Builder setAndroidTextStyle(androidx.wear.protolayout.LayoutElementBuilders.AndroidTextStyle);
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.Text.Builder setFontStyle(androidx.wear.protolayout.LayoutElementBuilders.FontStyle);
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=200) public androidx.wear.protolayout.LayoutElementBuilders.Text.Builder setLayoutConstraintsForDynamicText(androidx.wear.protolayout.TypeBuilders.StringLayoutConstraint);
     method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.Text.Builder setLineHeight(androidx.wear.protolayout.DimensionBuilders.SpProp);
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ColorBuilders.java b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ColorBuilders.java
index f14f152..cfcce8d 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ColorBuilders.java
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ColorBuilders.java
@@ -263,7 +263,7 @@
              */
             @RequiresSchemaVersion(major = 1, minor = 300)
             @NonNull
-            private Builder setColor(@NonNull ColorProp color) {
+            Builder setColor(@NonNull ColorProp color) {
                 if (color.getDynamicValue() != null) {
                     throw new IllegalArgumentException(
                             "ColorStop.Builder.setColor doesn't support dynamic values.");
@@ -284,7 +284,7 @@
              */
             @RequiresSchemaVersion(major = 1, minor = 300)
             @NonNull
-            public Builder setOffset(@NonNull FloatProp offset) {
+            Builder setOffset(@NonNull FloatProp offset) {
                 if (offset.getDynamicValue() != null) {
                     throw new IllegalArgumentException(
                             "ColorStop.Builder.setOffset doesn't support dynamic values.");
@@ -303,18 +303,22 @@
             /**
              * Creates an instance of {@link Builder}.
              *
-             * <p>If all {@link ColorStop} in a Gradient have no offset, the colors are evenly
-             * distributed in the gradient.
-             *
              * @param color the color for this stop. Only opaque colors are supported. Any
              *     transparent colors will have their alpha component set to 0xFF (opaque). Note
              *     that this parameter only supports static values.
+             * @param offset the relative offset for this color, between 0 and 1. This determines
+             *     where the color is positioned relative to a gradient space. Note that this
+             *     parameter only supports static values.
              */
             @RequiresSchemaVersion(major = 1, minor = 300)
-            public Builder(@NonNull ColorProp color) {
+            public Builder(@NonNull ColorProp color, @NonNull FloatProp offset) {
                 this.setColor(color);
+                this.setOffset(offset);
             }
 
+            /** Creates an instance of {@link Builder}. */
+            Builder() {}
+
             /** Builds an instance from accumulated values. */
             @NonNull
             public ColorStop build() {
@@ -524,16 +528,12 @@
              * Creates an instance of {@link Builder}.
              *
              * @param colorStops The color stops defining how the colors are distributed around the
-             *     gradient center. The color sequence starts at the start angle and spans 360
-             *     degrees clockwise, finishing at the same angle.
+             *     gradient center.
              *     <p>A color stop is composed of a color and its offset in the gradient. The offset
              *     is the relative position of the color, beginning with 0 from the start angle and
              *     ending with 1.0 at the end angle, spanning clockwise.
-             *     <p>If offsets are not set, the colors are evenly distributed in the gradient.
              * @throws IllegalArgumentException if the number of colors is less than 2 or larger
              *     than 10.
-             * @throws IllegalArgumentException if offsets in {@code colorStops} are partially set.
-             *     Either all or none of the {@link ColorStop} parameters should have an offset.
              */
             @RequiresSchemaVersion(major = 1, minor = 300)
             @SafeVarargs
@@ -543,18 +543,37 @@
                             "Size of colorStops must not be less than 2 or greater than 10. Got "
                                     + colorStops.length);
                 }
-                boolean offsetsShouldBePresent = colorStops[0].getOffset() != null;
                 for (ColorStop colorStop : colorStops) {
-                    boolean stopHasOffset = colorStop.getOffset() != null;
-                    if (offsetsShouldBePresent != stopHasOffset) {
-                        throw new IllegalArgumentException(
-                                "Either all or none of the colorStops should have an offset.");
-                    }
                     addColorStop(colorStop);
                 }
             }
 
             /**
+             * Creates an instance of {@link Builder}.
+             *
+             * <p>The colors are evenly distributed in the gradient.
+             *
+             * @param colors The color sequence to be distributed around the gradient center. The
+             *     color sequence is distributed between the gradient's start and end angles.
+             *
+             * @throws IllegalArgumentException if the number of colors is less than 2 or larger
+             *     than 10.
+             */
+            @RequiresSchemaVersion(major = 1, minor = 300)
+            @SafeVarargs
+            public Builder(@NonNull ColorProp... colors) {
+                if (colors.length < 2 || colors.length > 10) {
+                    throw new IllegalStateException(
+                            "Size of colors must not be less than 2 or greater than 10. Got "
+                                    + colors.length);
+                }
+                for (ColorProp colorProp : colors) {
+                    ColorStop stop = new ColorStop.Builder().setColor(colorProp).build();
+                    addColorStop(stop);
+                }
+            }
+
+            /**
              * Builds an instance from accumulated values.
              *
              * @throws IllegalStateException if size of colorStops is less than 2 or greater than
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
index dd99305..e0aab79 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
@@ -202,7 +202,10 @@
      * Truncate the text at the last line defined by {@code setMaxLines} in {@link Text} to fit in
      * the {@link Text} element's bounds, but add an ellipsis (i.e. ...) to the end of the text if
      * it has been truncated.
+     *
+     * @deprecated Use {@link #TEXT_OVERFLOW_ELLIPSIZE} instead.
      */
+    @Deprecated
     @RequiresSchemaVersion(major = 1, minor = 0)
     public static final int TEXT_OVERFLOW_ELLIPSIZE_END = 2;
 
@@ -222,6 +225,9 @@
      * parent container. Note that, when this is used, the parent of the {@link Text} element this
      * corresponds to shouldn't have its width and height set to wrapped, as it can lead to
      * unexpected results.
+     *
+     * <p>Note that, on {@link SpanText}, this will behave exactly the same way as
+     * TEXT_OVERFLOW_ELLIPSIZE_END.
      */
     @RequiresSchemaVersion(major = 1, minor = 300)
     public static final int TEXT_OVERFLOW_ELLIPSIZE = 4;
@@ -1100,6 +1106,7 @@
      */
     @RequiresSchemaVersion(major = 1, minor = 200)
     @ProtoLayoutExperimental
+    @RestrictTo(Scope.LIBRARY_GROUP)
     public static final class AndroidTextStyle {
         private final LayoutElementProto.AndroidTextStyle mImpl;
         @Nullable private final Fingerprint mFingerprint;
@@ -1159,7 +1166,11 @@
             private final Fingerprint mFingerprint = new Fingerprint(408674745);
 
             /** Creates an instance of {@link Builder}. */
-            public Builder() {}
+            public Builder() {
+                // Setting this to true before setter is called, so that default behaviour is to
+                // exclude padding.
+                mImpl.setExcludeFontPadding(true);
+            }
 
             /**
              * Sets whether the {@link Text} excludes padding specified by the font, i.e. extra top
@@ -1291,20 +1302,6 @@
         }
 
         /**
-         * Gets an Android platform specific text style configuration options for styling and
-         * compatibility.
-         */
-        @ProtoLayoutExperimental
-        @Nullable
-        public AndroidTextStyle getAndroidTextStyle() {
-            if (mImpl.hasAndroidTextStyle()) {
-                return AndroidTextStyle.fromProto(mImpl.getAndroidTextStyle());
-            } else {
-                return null;
-            }
-        }
-
-        /**
          * Gets the number of times to repeat the Marquee animation. Only applies when overflow is
          * TEXT_OVERFLOW_MARQUEE. Set to -1 to repeat indefinitely. Defaults to repeat indefinitely.
          */
@@ -1380,8 +1377,6 @@
                     + getOverflow()
                     + ", lineHeight="
                     + getLineHeight()
-                    + ", androidTextStyle="
-                    + getAndroidTextStyle()
                     + "}";
         }
 
@@ -1392,7 +1387,11 @@
             private final Fingerprint mFingerprint = new Fingerprint(814133697);
 
             /** Creates an instance of {@link Builder}. */
-            public Builder() {}
+            public Builder() {
+                mImpl.setAndroidTextStyle(
+                        LayoutElementProto.AndroidTextStyle.newBuilder()
+                                .setExcludeFontPadding(true));
+            }
 
             /**
              * Sets the text to render.
@@ -1535,20 +1534,6 @@
             }
 
             /**
-             * Sets an Android platform specific text style configuration options for styling and
-             * compatibility.
-             */
-            @RequiresSchemaVersion(major = 1, minor = 200)
-            @ProtoLayoutExperimental
-            @NonNull
-            public Builder setAndroidTextStyle(@NonNull AndroidTextStyle androidTextStyle) {
-                mImpl.setAndroidTextStyle(androidTextStyle.toProto());
-                mFingerprint.recordPropertyUpdate(
-                        8, checkNotNull(androidTextStyle.getFingerprint()).aggregateValueAsInt());
-                return this;
-            }
-
-            /**
              * Sets the number of times to repeat the Marquee animation. Only applies when overflow
              * is TEXT_OVERFLOW_MARQUEE. Set to -1 to repeat indefinitely. Defaults to repeat
              * indefinitely.
@@ -2590,20 +2575,6 @@
             }
         }
 
-        /**
-         * Gets an Android platform specific text style configuration options for styling and
-         * compatibility.
-         */
-        @ProtoLayoutExperimental
-        @Nullable
-        public AndroidTextStyle getAndroidTextStyle() {
-            if (mImpl.hasAndroidTextStyle()) {
-                return AndroidTextStyle.fromProto(mImpl.getAndroidTextStyle());
-            } else {
-                return null;
-            }
-        }
-
         @Override
         @RestrictTo(Scope.LIBRARY_GROUP)
         @Nullable
@@ -2649,8 +2620,6 @@
                     + getFontStyle()
                     + ", modifiers="
                     + getModifiers()
-                    + ", androidTextStyle="
-                    + getAndroidTextStyle()
                     + "}";
         }
 
@@ -2661,7 +2630,11 @@
             private final Fingerprint mFingerprint = new Fingerprint(266451531);
 
             /** Creates an instance of {@link Builder}. */
-            public Builder() {}
+            public Builder() {
+                mImpl.setAndroidTextStyle(
+                        LayoutElementProto.AndroidTextStyle.newBuilder()
+                                .setExcludeFontPadding(true));
+            }
 
             /**
              * Sets the text to render.
@@ -2718,20 +2691,6 @@
                 return this;
             }
 
-            /**
-             * Sets an Android platform specific text style configuration options for styling and
-             * compatibility.
-             */
-            @RequiresSchemaVersion(major = 1, minor = 200)
-            @ProtoLayoutExperimental
-            @NonNull
-            public Builder setAndroidTextStyle(@NonNull AndroidTextStyle androidTextStyle) {
-                mImpl.setAndroidTextStyle(androidTextStyle.toProto());
-                mFingerprint.recordPropertyUpdate(
-                        4, checkNotNull(androidTextStyle.getFingerprint()).aggregateValueAsInt());
-                return this;
-            }
-
             /** Builds an instance from accumulated values. */
             @Override
             @NonNull
diff --git a/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/LayoutElementBuildersTest.java b/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/LayoutElementBuildersTest.java
index a8352c8..e91a92d 100644
--- a/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/LayoutElementBuildersTest.java
+++ b/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/LayoutElementBuildersTest.java
@@ -271,6 +271,7 @@
     }
 
     @Test
+    @SuppressWarnings("deprecation")
     public void testTextSetOverflow_ellipsizeEnd() {
         LayoutElementBuilders.Text text =
                 new LayoutElementBuilders.Text.Builder()
diff --git a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileService.java b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileService.java
index 73fd138..06f7334 100644
--- a/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileService.java
+++ b/wear/tiles/tiles/src/main/java/androidx/wear/tiles/TileService.java
@@ -272,10 +272,10 @@
      * changed by the time the result is received. {@link TileService#onTileAddEvent} and {@link
      * TileService#onTileRemoveEvent} should be used instead for live updates.
      *
-     * <p>This method is a best-effort to match platform behavior, but may not always return all
-     * tiles present in the carousel. The possibly omitted tiles being the pre-installed tiles, all
-     * tiles if the user has cleared the app data, or the tiles a user hasn't visited in the last 60
-     * days, while tiles removed by an app update may be shown as active for 60 days afterwards.
+     * <p>This method may not always return all tiles present in the carousel. The possibly
+     * omitted tiles being the pre-installed tiles, all tiles if the user has cleared the app
+     * data, or the tiles a user hasn't visited in the last 60 days, while tiles removed by an
+     * app update may be shown as active for 60 days afterwards.
      *
      * @param context The application context.
      * @param executor The executor on which methods should be invoked. To dispatch events through
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/constraints/WorkConstraintsTracker.kt b/work/work-runtime/src/main/java/androidx/work/impl/constraints/WorkConstraintsTracker.kt
index 27e6336..7811847 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/constraints/WorkConstraintsTracker.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/constraints/WorkConstraintsTracker.kt
@@ -194,10 +194,17 @@
     override fun hasConstraint(workSpec: WorkSpec): Boolean =
         workSpec.constraints.requiredNetworkRequest != null
 
-    override fun isCurrentlyConstrained(workSpec: WorkSpec): Boolean =
+    override fun isCurrentlyConstrained(workSpec: WorkSpec): Boolean {
+        // It happens because ConstraintTrackingWorker can still run on API level 28
+        // after OS upgrade, because we're wrapping workers as ConstraintTrackingWorker at
+        // the enqueue time instead of execution time.
+        // However, ConstraintTrackingWorker won't have requiredNetworkRequest set
+        // because they were enqueued on APIs 23..25, in this case we don't throw.
+        if (!hasConstraint(workSpec)) return false
         throw IllegalStateException(
             "isCurrentlyConstrained() must never be called on" +
                 "NetworkRequestConstraintController. isCurrentlyConstrained() is called only " +
                 "on older platforms where NetworkRequest isn't supported"
         )
+    }
 }
diff --git a/work/work-runtime/src/test/java/androidx/work/NetworkRequestConstraintControllerTest.kt b/work/work-runtime/src/test/java/androidx/work/NetworkRequestConstraintControllerTest.kt
index e91bee2..c27890b 100644
--- a/work/work-runtime/src/test/java/androidx/work/NetworkRequestConstraintControllerTest.kt
+++ b/work/work-runtime/src/test/java/androidx/work/NetworkRequestConstraintControllerTest.kt
@@ -27,7 +27,9 @@
 import androidx.work.impl.constraints.ConstraintsState.ConstraintsMet
 import androidx.work.impl.constraints.ConstraintsState.ConstraintsNotMet
 import androidx.work.impl.constraints.NetworkRequestConstraintController
+import androidx.work.impl.model.WorkSpec
 import com.google.common.truth.Truth.assertThat
+import java.util.UUID
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.flow.collectIndexed
 import kotlinx.coroutines.flow.first
@@ -115,6 +117,15 @@
             )
         }
     }
+
+    @Test
+    fun testIsCurrentlyConstrained() {
+        val connectivityManager = getApplicationContext<Context>()
+            .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+        val controller = NetworkRequestConstraintController(connectivityManager, 0)
+        val workSpec = WorkSpec(id = UUID.randomUUID().toString(), workerClassName = "Foo")
+        assertThat(controller.isCurrentlyConstrained(workSpec)).isFalse()
+    }
 }
 
 @RequiresApi(28)